diff --git a/.github/workflows/performance-test.yaml b/.github/workflows/performance-test.yaml
index 65ca4f7063..76c24bf916 100644
--- a/.github/workflows/performance-test.yaml
+++ b/.github/workflows/performance-test.yaml
@@ -15,6 +15,7 @@ jobs:
os: [ubuntu-latest, macOS-latest]
runs-on: ${{ matrix.os }}
+ if: github.repository == 'projectdiscovery/nuclei'
steps:
- name: Set up Go
uses: actions/setup-go@v4
diff --git a/.github/workflows/publish-docs.yaml b/.github/workflows/publish-docs.yaml
index 2e30c3cb4f..da3a5110cd 100644
--- a/.github/workflows/publish-docs.yaml
+++ b/.github/workflows/publish-docs.yaml
@@ -29,7 +29,7 @@ jobs:
fi
go generate pkg/templates/templates.go
go build -o "cmd/docgen/docgen" cmd/docgen/docgen.go
- ./cmd/docgen/docgen ../SYNTAX-REFERENCE.md ../nuclei-jsonschema.json
+ ./cmd/docgen/docgen SYNTAX-REFERENCE.md nuclei-jsonschema.json
git status -s | wc -l | xargs -I {} echo CHANGES={} >> $GITHUB_OUTPUT
- name: Commit files
diff --git a/.gitignore b/.gitignore
index 31d516da03..57d2daa008 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,12 +20,19 @@ pkg/protocols/common/helpers/deserialization/testdata/ValueObject2.ser
.gitignore
pkg/js/devtools/bindgen/cmd/bindgen/bindgen
pkg/js/devtools/jsdocgen/jsdocgen
+pkg/js/devtools/tsgen/tsgen
+pkg/js/devtools/tsgen/cmd/tsgen/tsgen
*.DS_Store
pkg/protocols/headless/engine/.cache
/nuclei
/bindgen
/jsdocgen
+/tsgen
/scrapefuncs
/integration_tests/.cache/
-/integration_tests/.nuclei-config/
-/*.yaml
\ No newline at end of file
+/*.yaml
+**/*-config
+**/*-cache
+/fuzzplayground
+integration_tests/fuzzplayground
+
diff --git a/Makefile b/Makefile
index 6de70c7b1b..f6950b31a7 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,19 @@ 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 "tsgen" pkg/js/devtools/tsgen/cmd/tsgen/main.go
$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "scrapefuncs" pkg/js/devtools/scrapefuncs/main.go
+jsupdate:
+ $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "bindgen" pkg/js/devtools/bindgen/cmd/bindgen/main.go
+ $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "tsgen" pkg/js/devtools/tsgen/cmd/tsgen/main.go
+ ./bindgen -dir pkg/js/libs -out pkg/js/generated
+ ./tsgen -dir pkg/js/libs -out pkg/js/generated/ts
+ts:
+ $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "tsgen" pkg/js/devtools/tsgen/cmd/tsgen/main.go
+ ./tsgen -dir pkg/js/libs -out pkg/js/generated/ts
+fuzzplayground:
+ $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "fuzzplayground" cmd/tools/fuzzplayground/main.go
+memogen:
+ $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "memogen" cmd/memogen/memogen.go
+ ./memogen -src pkg/js/libs -tpl cmd/memogen/function.tpl
diff --git a/README.md b/README.md
index fa3edb36c5..e231221c2e 100644
--- a/README.md
+++ b/README.md
@@ -20,8 +20,6 @@
How •
Install •
- For Security Engineers •
- For Developers •
Documentation •
Credits •
FAQs •
@@ -115,12 +113,17 @@ Usage:
Flags:
TARGET:
- -u, -target string[] target URLs/hosts to scan
- -l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
- -eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
- -resume string resume scan using resume.cfg (clustering will be disabled)
- -sa, -scan-all-ips scan all the IP's associated with dns record
- -iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
+ -u, -target string[] target URLs/hosts to scan
+ -l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
+ -eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
+ -resume string resume scan using resume.cfg (clustering will be disabled)
+ -sa, -scan-all-ips scan all the IP's associated with dns record
+ -iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
+
+TARGET-FORMAT:
+ -im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
+ -ro, -required-only use only required fields in input format when generating requests
+ -sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
@@ -136,6 +139,7 @@ TEMPLATES:
-tl list all available templates
-sign signs the templates with the private key defined in NUCLEI_SIGNATURE_PRIVATE_KEY env variable
-code enable loading code protocol-based templates
+ -dut, -disable-unsigned-templates disable running unsigned templates or templates with mismatched signature
FILTERING:
-a, -author string[] templates to run based on authors (comma-separated, file)
@@ -144,8 +148,8 @@ FILTERING:
-itags, -include-tags string[] tags to be executed even if they are excluded either by default or configuration
-id, -template-id string[] templates to run based on template ids (comma-separated, file, allow-wildcard)
-eid, -exclude-id string[] templates to exclude based on template ids (comma-separated, file)
- -it, -include-templates string[] templates to be executed even if they are excluded either by default or configuration
- -et, -exclude-templates string[] template or template directory to exclude (comma-separated, file)
+ -it, -include-templates string[] path to template file or directory to be executed even if they are excluded either by default or configuration
+ -et, -exclude-templates string[] path to template file or directory to exclude (comma-separated, file)
-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
@@ -217,6 +221,7 @@ INTERACTSH:
FUZZING:
-ft, -fuzzing-type string overrides fuzzing type set in template (replace, prefix, postfix, infix)
-fm, -fuzzing-mode string overrides fuzzing mode set in template (multiple, single)
+ -fuzz enable loading fuzzing templates
UNCOVER:
-uc, -uncover enable uncover engine
@@ -233,6 +238,8 @@ RATE-LIMIT:
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
-hbs, -headless-bulk-size int maximum number of headless hosts to be analyzed in parallel per template (default 10)
-headc, -headless-concurrency int maximum number of headless templates to be executed in parallel (default 10)
+ -jsc, -js-concurrency int maximum number of javascript runtimes to be executed in parallel (default 120)
+ -pc, -payload-concurrency int max payload concurrency for each template (default 25)
OPTIMIZATIONS:
-timeout int time to wait in seconds before timeout (default 10)
@@ -294,22 +301,26 @@ CLOUD:
-cup, -cloud-upload upload scan results to pdcp dashboard
-sid, -scan-id string upload scan results to given scan id
+AUTHENTICATION:
+ -sf, -secret-file string[] path to config file containing secrets for nuclei authenticated scan
+ -ps, -prefetch-secrets prefetch secrets from the secrets file
+
EXAMPLES:
Run nuclei on single host:
- $ nuclei -target example.com
+ $ nuclei -target example.com
Run nuclei with specific template directories:
- $ nuclei -target example.com -t http/cves/ -t ssl
+ $ nuclei -target example.com -t http/cves/ -t ssl
Run nuclei against a list of hosts:
- $ nuclei -list hosts.txt
+ $ nuclei -list hosts.txt
Run nuclei with a JSON output:
- $ nuclei -target example.com -json-export output.json
+ $ nuclei -target example.com -json-export output.json
Run nuclei with sorted Markdown outputs (with environment variables):
- $ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
+ $ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
Additional documentation is available at: https://docs.nuclei.sh/getting-started/running
```
diff --git a/SYNTAX-REFERENCE.md b/SYNTAX-REFERENCE.md
index c55ea94eed..2433ae2138 100755
--- a/SYNTAX-REFERENCE.md
+++ b/SYNTAX-REFERENCE.md
@@ -1372,6 +1372,19 @@ Fuzzing describes schema to fuzz http requests
+self-contained
bool
+
+
+
+
+SelfContained specifies if the request is self-contained.
+
+
+
+
+
+
@@ -1390,12 +1403,12 @@ Valid values:
-disable-cookie
bool
+cookie-reuse
bool
-DisableCookie is an optional setting that disables cookie reuse for
+CookieReuse is an optional setting that enables cookie reuse for
all requests defined in raw section.
@@ -1404,6 +1417,19 @@ all requests defined in raw section.
+disable-cookie
bool
+
+
+
+
+DisableCookie is an optional setting that disables cookie reuse
+
+
+
+
+
+
+
read-all
bool
@@ -1585,6 +1611,32 @@ DisablePathAutomerge disables merging target url path with raw request path
+
+
+
+Filter is matcher-like field to check if fuzzing should be performed on this request or not
+
+
+
+
+
+
+
+filters-condition
string
+
+
+
+
+Filter condition is the condition to apply on the filter (AND/OR). Default is OR
+
+
+
+
+
@@ -1628,275 +1680,749 @@ Enum Values:
- batteringram
- - pitchfork
+ - pitchfork
+
+ - clusterbomb
+
+
+
+
+
+
+
+
+## HTTPMethodTypeHolder
+HTTPMethodTypeHolder is used to hold internal type of the HTTP Method
+
+Appears in:
+
+
+- http.Request .method
+
+
+
+
+
+
+
+
+
+
HTTPMethodType
+
+
+
+
+
+
+
+Enum Values:
+
+
+ - GET
+
+ - HEAD
+
+ - POST
+
+ - PUT
+
+ - DELETE
+
+ - CONNECT
+
+ - OPTIONS
+
+ - TRACE
+
+ - PATCH
+
+ - PURGE
+
+ - Debug
+
+
+
+
+
+
+
+
+## fuzz.Rule
+Rule is a single rule which describes how to fuzz the request
+
+Appears in:
+
+
+- http.Request .fuzzing
+
+- headless.Request .fuzzing
+
+
+
+
+
+
+
+
+
+type
string
+
+
+
+
+Type is the type of fuzzing rule to perform.
+
+replace replaces the values entirely. prefix prefixes the value. postfix postfixes the value
+and infix places between the values.
+
+
+Valid values:
+
+
+ - replace
+
+ - prefix
+
+ - postfix
+
+ - infix
+
+
+
+
+
+
+part
string
+
+
+
+
+Part is the part of request to fuzz.
+
+query fuzzes the query part of url. More parts will be added later.
+
+
+Valid values:
+
+
+ - query
+
+
+
+
+
+
+mode
string
+
+
+
+
+Mode is the mode of fuzzing to perform.
+
+single fuzzes one value at a time. multiple fuzzes all values at same time.
+
+
+Valid values:
+
+
+ - single
+
+ - multiple
+
+
+
+
+
+
+keys
[]string
+
+
+
+
+Keys is the optional list of key named parameters to fuzz.
+
+
+
+Examples:
+
+
+```yaml
+# Examples of keys
+keys:
+ - url
+ - file
+ - host
+```
+
+
+
+
+
+
+
+
+keys-regex
[]string
+
+
+
+
+KeysRegex is the optional list of regex key parameters to fuzz.
+
+
+
+Examples:
+
+
+```yaml
+# Examples of key regex
+keys-regex:
+ - url.*
+```
+
+
+
+
+
+
+
+
+values
[]string
+
+
+
+
+Values is the optional list of regex value parameters to fuzz.
+
+
+
+Examples:
+
+
+```yaml
+# Examples of value regex
+values:
+ - https?://.*
+```
+
+
+
+
+
+
+
+
+
+description: |
+ Fuzz is the list of payloads to perform substitutions with.
+ examples:
+ - name: Examples of fuzz
+ value: >
+ []string{"{{ssrf}}", "{{interactsh-url}}", "example-value"}
+ or
+ x-header: 1
+ x-header: 2
+
+
+
+
+
+
+
+replace-regex
string
+
+
+
+
+replace-regex is regex for regex-replace rule type
+it is only required for replace-regex rule type
+
+
+
+
+
+
+
+
+
+## SliceOrMapSlice
+
+Appears in:
+
+
+- fuzz.Rule .fuzz
+
+
+
+
+
+
+
+## SignatureTypeHolder
+SignatureTypeHolder is used to hold internal type of the signature
+
+Appears in:
+
+
+- http.Request .signature
+
+
+
+
+
+
+
+## matchers.Matcher
+Matcher is used to match a part in the output from a protocol.
+
+Appears in:
+
+
+- http.Request .filters
+
+
+
+
+
+
+
+
+
+
+Type is the type of the matcher.
+
+
+
+
+
+
+
+condition
string
+
+
+
+
+Condition is the optional condition between two matcher variables. By default,
+the condition is assumed to be OR.
+
+
+Valid values:
+
+
+ - and
+
+ - or
+
+
+
+
+
+
+part
string
+
+
+
+
+Part is the part of the request response to match data from.
+
+Each protocol exposes a lot of different parts which are well
+documented in docs for each request type.
+
+
+
+Examples:
+
+
+```yaml
+part: body
+```
+
+```yaml
+part: raw
+```
+
+
+
+
+
+
+
+
+negative
bool
+
+
+
+
+Negative specifies if the match should be reversed
+It will only match if the condition is not true.
+
+
+
+
+
+
+
+name
string
+
+
+
+
+Name of the matcher. Name should be lowercase and must not contain
+spaces or underscores (_).
+
+
+
+Examples:
+
+
+```yaml
+name: cookie-matcher
+```
+
+
+
+
+
+
+
+
+status
[]int
+
+
+
+
+Status are the acceptable status codes for the response.
+
+
+
+Examples:
+
+
+```yaml
+status:
+ - 200
+ - 302
+```
+
+
+
+
+
+
+
+
+size
[]int
+
+
+
+
+Size is the acceptable size for the response
+
+
+
+Examples:
+
+
+```yaml
+size:
+ - 3029
+ - 2042
+```
+
- - clusterbomb
+
+words
[]string
+
+
+Words contains word patterns required to be present in the response part.
-## HTTPMethodTypeHolder
-HTTPMethodTypeHolder is used to hold internal type of the HTTP Method
-Appears in:
+Examples:
--
http.Request .method
+```yaml
+# Match for Outlook mail protection domain
+words:
+ - mail.protection.outlook.com
+```
+```yaml
+# Match for application/json in response headers
+words:
+ - application/json
+```
+
-
HTTPMethodType
+regex
[]string
+Regex contains Regular Expression patterns required to be present in the response part.
-Enum Values:
-
-
- - GET
-
- - HEAD
-
- - POST
-
- - PUT
-
- - DELETE
-
- - CONNECT
+Examples:
- - OPTIONS
- - TRACE
+```yaml
+# Match for Linkerd Service via Regex
+regex:
+ - (?mi)^Via\\s*?:.*?linkerd.*$
+```
- - PATCH
+```yaml
+# Match for Open Redirect via Location header
+regex:
+ - (?m)^(?:Location\\s*?:\\s*?)(?:https?://|//)?(?:[a-zA-Z0-9\\-_\\.@]*)example\\.com.*$
+```
- - PURGE
- - Debug
+
+binary
[]string
+
+
+Binary are the binary patterns required to be present in the response part.
-## fuzz.Rule
-Rule is a single rule which describes how to fuzz the request
-
-Appears in:
--
http.Request .fuzzing
+Examples:
--
headless.Request .fuzzing
+```yaml
+# Match for Springboot Heapdump Actuator "JAVA PROFILE", "HPROF", "Gunzip magic byte"
+binary:
+ - 4a4156412050524f46494c45
+ - 4850524f46
+ - 1f8b080000000000
+```
+```yaml
+# Match for 7zip files
+binary:
+ - 377ABCAF271C
+```
+
-type
string
+dsl
[]string
-Type is the type of fuzzing rule to perform.
+DSL are the dsl expressions that will be evaluated as part of nuclei matching rules.
+A list of these helper functions are available [here](https://nuclei.projectdiscovery.io/templating-guide/helper-functions/).
-replace replaces the values entirely. prefix prefixes the value. postfix postfixes the value
-and infix places between the values.
-Valid values:
+Examples:
- - replace
+```yaml
+# DSL Matcher for package.json file
+dsl:
+ - contains(body, 'packages') && contains(tolower(all_headers), 'application/octet-stream') && status_code == 200
+```
- - prefix
+```yaml
+# DSL Matcher for missing strict transport security header
+dsl:
+ - '!contains(tolower(all_headers), ''''strict-transport-security'''')'
+```
- - postfix
- - infix
-part
string
+xpath
[]string
-Part is the part of request to fuzz.
+XPath are the xpath queries expressions that will be evaluated against the response part.
-query fuzzes the query part of url. More parts will be added later.
-Valid values:
+Examples:
+
+
+```yaml
+# XPath Matcher to check a title
+xpath:
+ - /html/head/title[contains(text(), 'How to Find XPath')]
+```
+
+```yaml
+# XPath Matcher for finding links with target="_blank"
+xpath:
+ - //a[@target="_blank"]
+```
- - query
-mode
string
+encoding
string
-Mode is the mode of fuzzing to perform.
-
-single fuzzes one value at a time. multiple fuzzes all values at same time.
+Encoding specifies the encoding for the words field if any.
Valid values:
- - single
-
- - multiple
+ - hex
-keys
[]string
+case-insensitive
bool
-Keys is the optional list of key named parameters to fuzz.
-
-
+CaseInsensitive enables case-insensitive matches. Default is false.
-Examples:
+Valid values:
-```yaml
-# Examples of keys
-keys:
- - url
- - file
- - host
-```
+ - false
+ - true
-keys-regex
[]string
+match-all
bool
-KeysRegex is the optional list of regex key parameters to fuzz.
-
+MatchAll enables matching for all matcher values. Default is false.
-Examples:
-
+Valid values:
-```yaml
-# Examples of key regex
-keys-regex:
- - url.*
-```
+ - false
+ - true
-values
[]string
+internal
bool
-Values is the optional list of regex value parameters to fuzz.
-
+description: |
+ Internal when true hides the matcher from output. Default is false.
+ It is meant to be used in multiprotocol / flow templates to create internal matcher condition without printing it in output.
+ or other similar use cases.
+ values:
+ - false
+ - true
+
-Examples:
+
-```yaml
-# Examples of value regex
-values:
- - https?://.*
-```
-
-
+## MatcherTypeHolder
+MatcherTypeHolder is used to hold internal type of the matcher
-
+Appears in:
-fuzz
[]string
-
-
+-
matchers.Matcher .type
-Fuzz is the list of payloads to perform substitutions with.
-Examples:
+
-```yaml
-# Examples of fuzz
-fuzz:
- - '{{ssrf}}'
- - '{{interactsh-url}}'
- - example-value
-```
+
+
MatcherType
+
-
+Enum Values:
-## SignatureTypeHolder
-SignatureTypeHolder is used to hold internal type of the signature
+ -
word
-Appears in:
+ -
regex
+ -
binary
--
http.Request .signature
+ -
status
+
+ -
size
+ -
dsl
+
+ -
xpath
+
+
@@ -2115,6 +2641,30 @@ 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.
+
+
+
+
+
+
+threads
int
+
+
+
+
+Threads to use when sending iterating over payloads
+
+
+
+Examples:
+
+
+```yaml
+# Send requests using 10 concurrent threads
+threads: 10
+```
+
+
@@ -2484,6 +3034,33 @@ 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.
+
+
+
+
+
+
+threads
int
+
+
+
+
+Threads specifies number of threads to use sending requests. This enables Connection Pooling.
+
+Connection: Close attribute must not be used in request while using threads flag, otherwise
+pooling will fail and engine will continue to close connections after requests.
+
+
+
+Examples:
+
+
+```yaml
+# Send requests using 10 concurrent threads
+threads: 10
+```
+
+
@@ -2881,6 +3458,19 @@ Fuzzing describes schema to fuzz headless requests
+cookie-reuse
bool
+
+
+
+
+CookieReuse is an optional setting that enables cookie reuse
+
+
+
+
+
+
+
disable-cookie
bool
@@ -3227,6 +3817,53 @@ description: |
+
+
+tls_version_enum
bool
+
+
+
+
+TLS Versions Enum - false if not specified
+Enumerates supported TLS versions
+
+
+
+
+
+
+
+tls_cipher_enum
bool
+
+
+
+
+TLS Ciphers Enum - false if not specified
+Enumerates supported TLS ciphers
+
+
+
+
+
+
+
+tls_cipher_types
[]string
+
+
+
+
+description: |
+ TLS Cipher types to enumerate
+ values:
+ - "insecure" (default)
+ - "weak"
+ - "secure"
+ - "all"
+
+
+
+
+
@@ -3649,6 +4286,19 @@ Code contains code to execute for the javascript request.
+timeout
int
+
+
+
+
+Timeout in seconds is optional timeout for each javascript script execution (i.e init, pre-condition, code)
+
+
+
+
+
+
+
stop-at-first-match
bool
diff --git a/cmd/integration-test/fuzz.go b/cmd/integration-test/fuzz.go
index 276f855030..be2ac16169 100644
--- a/cmd/integration-test/fuzz.go
+++ b/cmd/integration-test/fuzz.go
@@ -12,6 +12,10 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
+const (
+ targetFile = "fuzz/testData/ginandjuice.proxify.yaml"
+)
+
var fuzzingTestCases = []TestCaseInfo{
{Path: "fuzz/fuzz-mode.yaml", TestCase: &fuzzModeOverride{}},
{Path: "fuzz/fuzz-type.yaml", TestCase: &fuzzTypeOverride{}},
@@ -19,6 +23,29 @@ var fuzzingTestCases = []TestCaseInfo{
{Path: "fuzz/fuzz-headless.yaml", TestCase: &HeadlessFuzzingQuery{}},
{Path: "fuzz/fuzz-header-basic.yaml", TestCase: &FuzzHeaderBasic{}},
{Path: "fuzz/fuzz-header-multiple.yaml", TestCase: &FuzzHeaderMultiple{}},
+ // for fuzzing we should prioritize adding test case related backend
+ // logic in fuzz playground server instead of adding them here
+ {Path: "fuzz/fuzz-query-num-replace.yaml", TestCase: &genericFuzzTestCase{expectedResults: 2}},
+ {Path: "fuzz/fuzz-host-header-injection.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
+ {Path: "fuzz/fuzz-path-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
+ {Path: "fuzz/fuzz-cookie-error-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
+ {Path: "fuzz/fuzz-body-json-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
+ {Path: "fuzz/fuzz-body-multipart-form-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
+ {Path: "fuzz/fuzz-body-params-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
+ {Path: "fuzz/fuzz-body-xml-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
+ {Path: "fuzz/fuzz-body-generic-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 4}},
+}
+
+type genericFuzzTestCase struct {
+ expectedResults int
+}
+
+func (g *genericFuzzTestCase) Execute(filePath string) error {
+ results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-t", filePath, "-l", targetFile, "-im", "yaml")
+ if err != nil {
+ return err
+ }
+ return expectResultsCount(results, g.expectedResults)
}
type httpFuzzQuery struct{}
@@ -34,7 +61,7 @@ func (h *httpFuzzQuery) Execute(filePath string) error {
ts := httptest.NewTLSServer(router)
defer ts.Close()
- results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?id=example", debug)
+ results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?id=example", debug, "-fuzz")
if err != nil {
return err
}
@@ -53,7 +80,7 @@ func (h *fuzzModeOverride) Execute(filePath string) error {
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
- results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?id=example&name=nuclei", debug, "-fuzzing-mode", "single", "-jsonl")
+ results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?id=example&name=nuclei", debug, "-fuzzing-mode", "single", "-jsonl", "-fuzz")
if err != nil {
return err
}
@@ -98,7 +125,7 @@ func (h *fuzzTypeOverride) Execute(filePath string) error {
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
- results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"?id=example", debug, "-fuzzing-type", "replace", "-jsonl")
+ results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"?id=example", debug, "-fuzzing-type", "replace", "-jsonl", "-fuzz")
if err != nil {
return err
}
@@ -143,7 +170,7 @@ func (h *HeadlessFuzzingQuery) Execute(filePath string) error {
ts := httptest.NewTLSServer(router)
defer ts.Close()
- got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"?url=https://scanme.sh", debug, "-headless")
+ got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"?url=https://scanme.sh", debug, "-headless", "-fuzz")
if err != nil {
return err
}
@@ -164,7 +191,7 @@ func (h *FuzzHeaderBasic) Execute(filePath string) error {
ts := httptest.NewTLSServer(router)
defer ts.Close()
- got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
+ got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-fuzz")
if err != nil {
return err
}
@@ -192,7 +219,7 @@ func (h *FuzzHeaderMultiple) Execute(filePath string) error {
ts := httptest.NewTLSServer(router)
defer ts.Close()
- got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
+ got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-fuzz")
if err != nil {
return err
}
diff --git a/cmd/integration-test/http.go b/cmd/integration-test/http.go
index e1fddc04ad..41e1910a86 100644
--- a/cmd/integration-test/http.go
+++ b/cmd/integration-test/http.go
@@ -80,6 +80,55 @@ var httpTestcases = []TestCaseInfo{
{Path: "protocols/http/disable-path-automerge.yaml", TestCase: &httpDisablePathAutomerge{}},
{Path: "protocols/http/http-preprocessor.yaml", TestCase: &httpPreprocessor{}},
{Path: "protocols/http/multi-request.yaml", TestCase: &httpMultiRequest{}},
+ {Path: "protocols/http/http-matcher-extractor-dy-extractor.yaml", TestCase: &httpMatcherExtractorDynamicExtractor{}},
+ {Path: "protocols/http/multi-http-var-sharing.yaml", TestCase: &httpMultiVarSharing{}},
+}
+
+type httpMultiVarSharing struct{}
+
+func (h *httpMultiVarSharing) Execute(filePath string) error {
+ results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "https://scanme.sh", debug)
+ if err != nil {
+ return err
+ }
+ return expectResultsCount(results, 1)
+}
+
+type httpMatcherExtractorDynamicExtractor struct{}
+
+func (h *httpMatcherExtractorDynamicExtractor) Execute(filePath string) error {
+ router := httprouter.New()
+ router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+ html := `
+
+
+ Domains
+
+`
+ fmt.Fprint(w, html)
+ })
+ router.GET("/domains", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+ html := `
+
+
+ Dynamic Extractor Test
+
+
+
+
+
+ `
+ fmt.Fprint(w, html)
+ })
+ ts := httptest.NewServer(router)
+ defer ts.Close()
+
+ results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
+ if err != nil {
+ return err
+ }
+
+ return expectResultsCount(results, 1)
}
type httpInteractshRequest struct{}
@@ -776,8 +825,18 @@ func (h *httpPaths) Execute(filepath string) error {
}
}
- if !reflect.DeepEqual(expected, actual) {
- return fmt.Errorf("%8v: %v\n%-8v: %v", "expected", expected, "actual", actual)
+ if len(expected) > len(actual) {
+ actualValuesIndex := len(actual) - 1
+ if actualValuesIndex < 0 {
+ actualValuesIndex = 0
+ }
+ return fmt.Errorf("missing values : %v", expected[actualValuesIndex:])
+ } else if len(expected) < len(actual) {
+ return fmt.Errorf("unexpected values : %v", actual[len(expected)-1:])
+ } else {
+ if !reflect.DeepEqual(expected, actual) {
+ return fmt.Errorf("expected: %v\n\nactual: %v", expected, actual)
+ }
}
return nil
}
diff --git a/cmd/integration-test/integration-test.go b/cmd/integration-test/integration-test.go
index 672224e13f..7685141389 100644
--- a/cmd/integration-test/integration-test.go
+++ b/cmd/integration-test/integration-test.go
@@ -9,7 +9,9 @@ import (
"github.com/logrusorgru/aurora"
+ "github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
+ "github.com/projectdiscovery/nuclei/v3/pkg/testutils/fuzzplayground"
sliceutil "github.com/projectdiscovery/utils/slice"
)
@@ -53,6 +55,10 @@ var (
"flow": flowTestcases,
"javascript": jsTestcases,
}
+ // flakyTests are run with a retry count of 3
+ flakyTests = map[string]bool{
+ "protocols/http/self-contained-file-input.yaml": true,
+ }
// For debug purposes
runProtocol = ""
@@ -78,6 +84,18 @@ func main() {
os.Exit(1)
}
+ // start fuzz playground server
+ defer fuzzplayground.Cleanup()
+ server := fuzzplayground.GetPlaygroundServer()
+ defer server.Close()
+ go func() {
+ if err := server.Start("localhost:8082"); err != nil {
+ if !strings.Contains(err.Error(), "Server closed") {
+ gologger.Fatal().Msgf("Could not start server: %s\n", err)
+ }
+ }
+ }()
+
customTestsList := normalizeSplit(customTests)
failedTestTemplatePaths := runTests(customTestsList)
@@ -150,6 +168,8 @@ func runTests(customTemplatePaths []string) []string {
var err error
if proto == "interactsh" || strings.Contains(testCaseInfo.Path, "interactsh") {
failedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)
+ } else if flakyTests[testCaseInfo.Path] {
+ failedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)
} else {
failedTemplatePath, err = execute(testCaseInfo.TestCase, testCaseInfo.Path)
}
diff --git a/cmd/integration-test/library.go b/cmd/integration-test/library.go
index 62d88759b5..fd02f17ed4 100644
--- a/cmd/integration-test/library.go
+++ b/cmd/integration-test/library.go
@@ -19,11 +19,10 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
- "github.com/projectdiscovery/nuclei/v3/pkg/core/inputs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/parsers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
@@ -69,7 +68,7 @@ func executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error)
defer cache.Close()
mockProgress := &testutils.MockProgressClient{}
- reportingClient, err := reporting.New(&reporting.Options{}, "")
+ reportingClient, err := reporting.New(&reporting.Options{}, "", false)
if err != nil {
return nil, err
}
@@ -126,8 +125,7 @@ func executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error)
}
store.Load()
- input := &inputs.SimpleInputProvider{Inputs: []*contextargs.MetaInput{{Input: templateURL}}}
- _ = engine.Execute(store.Templates(), input)
+ _ = engine.Execute(store.Templates(), provider.NewSimpleInputProviderWithUrls(templateURL))
engine.WorkPool().Wait() // Wait for the scan to finish
return results, nil
diff --git a/cmd/integration-test/workflow.go b/cmd/integration-test/workflow.go
index c0ccb8ee8a..3ffd28871e 100644
--- a/cmd/integration-test/workflow.go
+++ b/cmd/integration-test/workflow.go
@@ -137,7 +137,7 @@ type workflowSharedCookies struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowSharedCookies) Execute(filePath string) error {
- handleFunc := func(name string, w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+ handleFunc := func(name string, w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
cookie := &http.Cookie{Name: name, Value: name}
http.SetCookie(w, cookie)
}
diff --git a/cmd/memogen/function.tpl b/cmd/memogen/function.tpl
new file mode 100644
index 0000000000..0b6155b735
--- /dev/null
+++ b/cmd/memogen/function.tpl
@@ -0,0 +1,28 @@
+// Warning - This is generated code
+package {{.SourcePackage}}
+
+import (
+ "github.com/projectdiscovery/utils/memoize"
+
+ {{range .Imports}}
+ {{.Name}} {{.Path}}
+ {{end}}
+)
+
+{{range .Functions}}
+ {{ .SignatureWithPrefix "memoized" }} {
+ hash := "{{ .Name }}" {{range .Params}} + ":" + fmt.Sprint({{.Name}}) {{end}}
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return {{.Name}}({{.ParamsNames}})
+ })
+ if err != nil {
+ return {{.ResultFirstFieldDefaultValue}}, err
+ }
+ if value, ok := v.({{.ResultFirstFieldType}}); ok {
+ return value, nil
+ }
+
+ return {{.ResultFirstFieldDefaultValue}}, errors.New("could not convert cached result")
+ }
+{{end}}
\ No newline at end of file
diff --git a/cmd/memogen/memogen.go b/cmd/memogen/memogen.go
new file mode 100644
index 0000000000..b494235ad4
--- /dev/null
+++ b/cmd/memogen/memogen.go
@@ -0,0 +1,77 @@
+// this small cli tool is specific for those functions with arbitrary parameters and with result-error tuple as return values
+// func(x,y) => result, error
+// it works by creating a new memoized version of the functions in the same path as memo.original.file.go
+// some parts are specific for nuclei and hardcoded within the template
+package main
+
+import (
+ "flag"
+ "io/fs"
+ "log"
+ "os"
+ "path/filepath"
+
+ "github.com/projectdiscovery/utils/memoize"
+ stringsutil "github.com/projectdiscovery/utils/strings"
+)
+
+var (
+ srcPath = flag.String("src", "", "nuclei source path")
+ tplPath = flag.String("tpl", "function.tpl", "template path")
+ tplSrc []byte
+)
+
+func main() {
+ flag.Parse()
+
+ var err error
+ tplSrc, err = os.ReadFile(*tplPath)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ err = filepath.Walk(*srcPath, walk)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func walk(path string, info fs.FileInfo, err error) error {
+ if info.IsDir() {
+ return nil
+ }
+
+ if err != nil {
+ return err
+ }
+
+ ext := filepath.Ext(path)
+ base := filepath.Base(path)
+
+ if !stringsutil.EqualFoldAny(ext, ".go") {
+ return nil
+ }
+
+ basePath := filepath.Dir(path)
+ outPath := filepath.Join(basePath, "memo."+base)
+
+ // filename := filepath.Base(path)
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ if !stringsutil.ContainsAnyI(string(data), "@memo") {
+ return nil
+ }
+ log.Println("processing:", path)
+ out, err := memoize.Src(string(tplSrc), path, data, "")
+ if err != nil {
+ return err
+ }
+
+ if err := os.WriteFile(outPath, out, os.ModePerm); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/cmd/nuclei/issue-tracker-config.yaml b/cmd/nuclei/issue-tracker-config.yaml
index 84c678b6e2..51778eb0ad 100644
--- a/cmd/nuclei/issue-tracker-config.yaml
+++ b/cmd/nuclei/issue-tracker-config.yaml
@@ -1,3 +1,8 @@
+# global allow/deny list. this will affect both exporters
+# as well as issue trackers. you can filter trackers with
+# a tracker level filter on top of an exporter by setting
+# allow-list/deny-list per tracker.
+#
#allow-list:
# severity: high, critical
#deny-list:
@@ -17,6 +22,15 @@
# project-name: test-project
# # issue-label is the label of the created issue type
# issue-label: bug
+# # allow-list sets a tracker level filter to only create issues for templates with
+# # these severity labels or tags (does not affect exporters. set those globally)
+# allow-list:
+# severity: high, critical
+# tags: network
+# # deny-list sets a tracker level filter to never create issues for templates with
+# # these severity labels or tags (does not affect exporters. set those globally)
+# deny-list:
+# severity: low
# # duplicate-issue-check flag to enable duplicate tracking issue check.
# duplicate-issue-check: false
#
@@ -32,6 +46,41 @@
# project-name: "1234"
# # issue-label is the label of the created issue type
# issue-label: bug
+# # allow-list sets a tracker level filter to only create issues for templates with
+# # these severity labels or tags (does not affect exporters. set those globally)
+# allow-list:
+# severity: high, critical
+# tags: network
+# # deny-list sets a tracker level filter to never create issues for templates with
+# # these severity labels or tags (does not affect exporters. set those globally)
+# deny-list:
+# severity: low
+#
+# Gitea contains configuration options for a gitea issue tracker
+#gitea:
+# # base-url is the optional self-hosted Gitea application url (defaults to https://gitea.com)
+# base-url: https://localhost:8443/
+# # token is the token for a Gitea account to use
+# token: test-token
+# # project-owner is the owner (user or org) of the repository
+# project-owner: "1234"
+# # project-name is the name of the repository
+# project-name: "1234"
+# # issue-label is a custom label to add to created issues
+# issue-label: bug
+# # severity-as-label (optional) adds the severity as a label of the created issue
+# severity-as-label: true
+# # allow-list sets a tracker level filter to only create issues for templates with
+# # these severity labels or tags (does not affect exporters. set those globally)
+# allow-list:
+# severity: high, critical
+# tags: network
+# # deny-list sets a tracker level filter to never create issues for templates with
+# # these severity labels or tags (does not affect exporters. set those globally)
+# deny-list:
+# severity: low
+# # duplicate-issue-check (optional) flag to enable duplicate tracking issue check
+# duplicate-issue-check: false
#
# Jira contains configuration options for Jira issue tracker
#jira:
@@ -54,6 +103,15 @@
# # SeverityAsLabel (optional) sends the severity as the label of the created issue
# # User custom fields for Jira Cloud instead
# severity-as-label: true
+# # allow-list sets a tracker level filter to only create issues for templates with
+# # these severity labels or tags (does not affect exporters. set those globally)
+# allow-list:
+# severity: high, critical
+# tags: network
+# # deny-list sets a tracker level filter to never create issues for templates with
+# # these severity labels or tags (does not affect exporters. set those globally)
+# deny-list:
+# severity: low
# # Whatever your final status is that you want to use as a closed ticket - Closed, Done, Remediated, etc
# # When checking for duplicates, the JQL query will filter out status's that match this.
# # If it finds a match _and_ the ticket does have this status, a new one will be created.
diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go
index fd744c0579..731865d7e6 100644
--- a/cmd/nuclei/main.go
+++ b/cmd/nuclei/main.go
@@ -13,6 +13,7 @@ import (
"time"
"github.com/projectdiscovery/utils/auth/pdcp"
+ "github.com/projectdiscovery/utils/env"
_ "github.com/projectdiscovery/utils/pprof"
"github.com/projectdiscovery/goflags"
@@ -21,6 +22,7 @@ import (
"github.com/projectdiscovery/interactsh/pkg/client"
"github.com/projectdiscovery/nuclei/v3/internal/runner"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/installer"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
@@ -45,6 +47,9 @@ var (
)
func main() {
+ // enables CLI specific configs mostly interactive behavior
+ config.CurrentAppMode = config.AppModeCLI
+
if err := runner.ConfigureOptions(); err != nil {
gologger.Fatal().Msgf("Could not initialize options: %s\n", err)
}
@@ -180,7 +185,7 @@ func readConfig() *goflags.FlagSet {
// when true updates nuclei binary to latest version
var updateNucleiBinary bool
- var pdcpauth bool
+ var pdcpauth string
flagSet := goflags.NewFlagSet()
flagSet.CaseSensitive = true
@@ -200,6 +205,12 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.StringSliceVarP(&options.IPVersion, "ip-version", "iv", nil, "IP version to scan of hostname (4,6) - (default 4)", goflags.CommaSeparatedStringSliceOptions),
)
+ flagSet.CreateGroup("target-format", "Target-Format",
+ flagSet.StringVarP(&options.InputFileMode, "input-mode", "im", "list", fmt.Sprintf("mode of input file (%v)", provider.SupportedInputFormats())),
+ flagSet.BoolVarP(&options.FormatUseRequiredOnly, "required-only", "ro", false, "use only required fields in input format when generating requests"),
+ flagSet.BoolVarP(&options.SkipFormatValidation, "skip-format-validation", "sfv", false, "skip format validation (like missing vars) when parsing input file"),
+ )
+
flagSet.CreateGroup("templates", "Templates",
flagSet.BoolVarP(&options.NewTemplates, "new-templates", "nt", false, "run only new templates added in latest nuclei-templates release"),
flagSet.StringSliceVarP(&options.NewTemplatesWithVersion, "new-templates-version", "ntv", nil, "run new templates added in specific version", goflags.CommaSeparatedStringSliceOptions),
@@ -215,6 +226,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.StringSliceVarConfigOnly(&options.RemoteTemplateDomainList, "remote-template-domain", []string{"cloud.projectdiscovery.io"}, "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.BoolVar(&options.EnableCodeTemplates, "code", false, "enable loading code protocol-based templates"),
+ flagSet.BoolVarP(&options.DisableUnsignedTemplates, "disable-unsigned-templates", "dut", false, "disable running unsigned templates or templates with mismatched signature"),
)
flagSet.CreateGroup("filters", "Filtering",
@@ -224,8 +236,8 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.StringSliceVarP(&options.IncludeTags, "include-tags", "itags", nil, "tags to be executed even if they are excluded either by default or configuration", goflags.FileNormalizedStringSliceOptions), // TODO show default deny list
flagSet.StringSliceVarP(&options.IncludeIds, "template-id", "id", nil, "templates to run based on template ids (comma-separated, file, allow-wildcard)", goflags.FileNormalizedStringSliceOptions),
flagSet.StringSliceVarP(&options.ExcludeIds, "exclude-id", "eid", nil, "templates to exclude based on template ids (comma-separated, file)", goflags.FileNormalizedStringSliceOptions),
- flagSet.StringSliceVarP(&options.IncludeTemplates, "include-templates", "it", nil, "templates to be executed even if they are excluded either by default or configuration", goflags.FileCommaSeparatedStringSliceOptions),
- flagSet.StringSliceVarP(&options.ExcludedTemplates, "exclude-templates", "et", nil, "template or template directory to exclude (comma-separated, file)", goflags.FileCommaSeparatedStringSliceOptions),
+ flagSet.StringSliceVarP(&options.IncludeTemplates, "include-templates", "it", nil, "path to template file or directory to be executed even if they are excluded either by default or configuration", goflags.FileCommaSeparatedStringSliceOptions),
+ flagSet.StringSliceVarP(&options.ExcludedTemplates, "exclude-templates", "et", nil, "path to template file or directory to exclude (comma-separated, file)", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.StringSliceVarP(&options.ExcludeMatchers, "exclude-matchers", "em", nil, "template matchers to exclude in result", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.VarP(&options.Severities, "severity", "s", fmt.Sprintf("templates to run based on severity. Possible values: %s", severity.GetSupportedSeverities().String())),
flagSet.VarP(&options.ExcludeSeverities, "exclude-severity", "es", fmt.Sprintf("templates to exclude based on severity. Possible values: %s", severity.GetSupportedSeverities().String())),
@@ -301,6 +313,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.CreateGroup("fuzzing", "Fuzzing",
flagSet.StringVarP(&options.FuzzingType, "fuzzing-type", "ft", "", "overrides fuzzing type set in template (replace, prefix, postfix, infix)"),
flagSet.StringVarP(&options.FuzzingMode, "fuzzing-mode", "fm", "", "overrides fuzzing mode set in template (multiple, single)"),
+ flagSet.BoolVar(&options.FuzzTemplates, "fuzz", false, "enable loading fuzzing templates"),
)
flagSet.CreateGroup("uncover", "Uncover",
@@ -320,6 +333,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.IntVarP(&options.HeadlessBulkSize, "headless-bulk-size", "hbs", 10, "maximum number of headless hosts to be analyzed in parallel per template"),
flagSet.IntVarP(&options.HeadlessTemplateThreads, "headless-concurrency", "headc", 10, "maximum number of headless templates to be executed in parallel"),
flagSet.IntVarP(&options.JsConcurrency, "js-concurrency", "jsc", 120, "maximum number of javascript runtimes to be executed in parallel"),
+ flagSet.IntVarP(&options.PayloadConcurrency, "payload-concurrency", "pc", 25, "max payload concurrency for each template"),
)
flagSet.CreateGroup("optimization", "Optimizations",
flagSet.IntVar(&options.Timeout, "timeout", 10, "time to wait in seconds before timeout"),
@@ -386,11 +400,16 @@ on extensive configurability, massive extensibility and ease of use.`)
)
flagSet.CreateGroup("cloud", "Cloud",
- flagSet.BoolVar(&pdcpauth, "auth", false, "configure projectdiscovery cloud (pdcp) api key"),
+ flagSet.DynamicVar(&pdcpauth, "auth", "true", "configure projectdiscovery cloud (pdcp) api key"),
flagSet.BoolVarP(&options.EnableCloudUpload, "cloud-upload", "cup", false, "upload scan results to pdcp dashboard"),
flagSet.StringVarP(&options.ScanID, "scan-id", "sid", "", "upload scan results to given scan id"),
)
+ flagSet.CreateGroup("Authentication", "Authentication",
+ flagSet.StringSliceVarP(&options.SecretsFile, "secret-file", "sf", nil, "path to config file containing secrets for nuclei authenticated scan", goflags.CommaSeparatedStringSliceOptions),
+ flagSet.BoolVarP(&options.PreFetchSecrets, "prefetch-secrets", "ps", false, "prefetch secrets from the secrets file"),
+ )
+
flagSet.SetCustomHelpText(`EXAMPLES:
Run nuclei on single host:
$ nuclei -target example.com
@@ -417,8 +436,17 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
goflags.DisableAutoConfigMigration = true
_ = flagSet.Parse()
- if pdcpauth {
+ // api key hierarchy: cli flag > env var > .pdcp/credential file
+ if pdcpauth == "true" {
runner.AuthWithPDCP()
+ } else if len(pdcpauth) == 36 {
+ ph := pdcp.PDCPCredHandler{}
+ if _, err := ph.GetCreds(); err == pdcp.ErrNoCreds {
+ apiServer := env.GetEnvOrDefault("PDCP_API_SERVER", pdcp.DefaultApiServer)
+ if validatedCreds, err := ph.ValidateAPIKey(pdcpauth, apiServer, config.BinaryName); err == nil {
+ _ = ph.SaveCreds(validatedCreds)
+ }
+ }
}
gologger.DefaultLogger.SetTimestamp(options.Timestamp, levels.LevelDebug)
@@ -456,6 +484,14 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
config.DefaultConfig.SetTemplatesDir(options.NewTemplatesDirectory)
}
+ if len(options.SecretsFile) > 0 {
+ for _, secretFile := range options.SecretsFile {
+ if !fileutil.FileExists(secretFile) {
+ gologger.Fatal().Msgf("given secrets file '%s' does not exist", options.SecretsFile)
+ }
+ }
+ }
+
cleanupOldResumeFiles()
return flagSet
}
diff --git a/cmd/tools/fuzzplayground/main.go b/cmd/tools/fuzzplayground/main.go
index 4298b12a57..0ab764e8b6 100644
--- a/cmd/tools/fuzzplayground/main.go
+++ b/cmd/tools/fuzzplayground/main.go
@@ -1,94 +1,27 @@
package main
import (
- "fmt"
- "io"
- "os/exec"
- "strconv"
- "strings"
+ "flag"
- "github.com/labstack/echo/v4"
- "github.com/labstack/echo/v4/middleware"
- "github.com/projectdiscovery/retryablehttp-go"
+ _ "github.com/mattn/go-sqlite3"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/testutils/fuzzplayground"
)
-func main() {
- e := echo.New()
- e.Use(middleware.Recover())
- e.Use(middleware.Logger())
-
- e.GET("/", indexHandler)
- e.GET("/info", infoHandler)
- e.GET("/redirect", redirectHandler)
- e.GET("/request", requestHandler)
- e.GET("/email", emailHandler)
- e.GET("/permissions", permissionsHandler)
- if err := e.Start("localhost:8082"); err != nil {
- panic(err)
- }
-}
-
-var bodyTemplate = `
-
-Fuzz Playground
-
-
-%s
-
-`
-
-func indexHandler(ctx echo.Context) error {
- return ctx.HTML(200, fmt.Sprintf(bodyTemplate, `Fuzzing Playground
-
-`))
-}
-
-func infoHandler(ctx echo.Context) error {
- return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Name of user: %s%s%s", ctx.QueryParam("name"), ctx.QueryParam("another"), ctx.QueryParam("random"))))
-}
+var (
+ addr string
+)
-func redirectHandler(ctx echo.Context) error {
- url := ctx.QueryParam("redirect_url")
- return ctx.Redirect(302, url)
-}
+func main() {
+ flag.StringVar(&addr, "addr", "localhost:8082", "playground server address")
+ flag.Parse()
-func requestHandler(ctx echo.Context) error {
- url := ctx.QueryParam("url")
- data, err := retryablehttp.DefaultClient().Get(url)
- if err != nil {
- return ctx.HTML(500, err.Error())
- }
- defer data.Body.Close()
+ defer fuzzplayground.Cleanup()
+ server := fuzzplayground.GetPlaygroundServer()
+ defer server.Close()
- body, _ := io.ReadAll(data.Body)
- return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(body)))
-}
-
-func emailHandler(ctx echo.Context) error {
- text := ctx.QueryParam("text")
- if strings.Contains(text, "{{") {
- trimmed := strings.SplitN(strings.Trim(text[strings.Index(text, "{"):], "{}"), "*", 2)
- if len(trimmed) < 2 {
- return ctx.HTML(500, "invalid template")
- }
- first, _ := strconv.Atoi(trimmed[0])
- second, _ := strconv.Atoi(trimmed[1])
- text = strconv.Itoa(first * second)
+ // Start the server
+ if err := server.Start(addr); err != nil {
+ gologger.Fatal().Msgf("Could not start server: %s\n", err)
}
- return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Text: %s", text)))
-}
-
-func permissionsHandler(ctx echo.Context) error {
- command := ctx.QueryParam("cmd")
- fields := strings.Fields(command)
- cmd := exec.Command(fields[0], fields[1:]...)
- data, _ := cmd.CombinedOutput()
-
- return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(data)))
}
diff --git a/go.mod b/go.mod
index ea37cfc992..7b52027d7d 100644
--- a/go.mod
+++ b/go.mod
@@ -20,12 +20,12 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/pkg/errors v0.9.1
github.com/projectdiscovery/clistats v0.0.20
- github.com/projectdiscovery/fastdialer v0.0.57
- github.com/projectdiscovery/hmap v0.0.36
- github.com/projectdiscovery/interactsh v1.1.8
- github.com/projectdiscovery/rawhttp v0.1.35
- github.com/projectdiscovery/retryabledns v1.0.53
- github.com/projectdiscovery/retryablehttp-go v1.0.44
+ github.com/projectdiscovery/fastdialer v0.0.62
+ github.com/projectdiscovery/hmap v0.0.41
+ github.com/projectdiscovery/interactsh v1.1.9
+ github.com/projectdiscovery/rawhttp v0.1.40
+ github.com/projectdiscovery/retryabledns v1.0.58
+ github.com/projectdiscovery/retryablehttp-go v1.0.51
github.com/projectdiscovery/yamldoc-go v1.0.4
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/xid v1.5.0
@@ -38,14 +38,14 @@ require (
github.com/weppos/publicsuffix-go v0.30.2-0.20230730094716-a20f9abcc222
github.com/xanzy/go-gitlab v0.84.0
go.uber.org/multierr v1.11.0
- golang.org/x/net v0.20.0
+ golang.org/x/net v0.21.0
golang.org/x/oauth2 v0.11.0
golang.org/x/text v0.14.0
gopkg.in/yaml.v2 v2.4.0
- moul.io/http2curl v1.0.0
)
require (
+ code.gitea.io/sdk/gitea v0.17.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
github.com/DataDog/gostackparse v0.6.0
@@ -59,47 +59,52 @@ 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/clbanning/mxj/v2 v2.7.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/getkin/kin-openapi v0.123.0
github.com/go-git/go-git/v5 v5.11.0
github.com/go-ldap/ldap/v3 v3.4.5
github.com/go-pg/pg v8.0.7+incompatible
github.com/go-sql-driver/mysql v1.7.1
github.com/h2non/filetype v1.1.3
github.com/labstack/echo/v4 v4.10.2
+ github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa
github.com/lib/pq v1.10.1
+ github.com/mattn/go-sqlite3 v1.14.22
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.41
+ github.com/projectdiscovery/dsl v0.0.46
github.com/projectdiscovery/fasttemplate v0.0.2
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
- github.com/projectdiscovery/goflags v0.1.37
+ github.com/projectdiscovery/goflags v0.1.42
github.com/projectdiscovery/gologger v1.1.12
github.com/projectdiscovery/gostruct v0.0.2
- github.com/projectdiscovery/gozero v0.0.1
- github.com/projectdiscovery/httpx v1.3.8
+ github.com/projectdiscovery/gozero v0.0.2
+ github.com/projectdiscovery/httpx v1.6.0
github.com/projectdiscovery/mapcidr v1.1.16
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
- github.com/projectdiscovery/ratelimit v0.0.26
+ github.com/projectdiscovery/ratelimit v0.0.27
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917
github.com/projectdiscovery/sarif v0.0.1
github.com/projectdiscovery/tlsx v1.1.6
github.com/projectdiscovery/uncover v1.0.7
- github.com/projectdiscovery/useragent v0.0.35
- github.com/projectdiscovery/utils v0.0.76
- github.com/projectdiscovery/wappalyzergo v0.0.109
+ github.com/projectdiscovery/useragent v0.0.40
+ github.com/projectdiscovery/utils v0.0.84-0.20240312214300-d3ba70dbb9ca
+ github.com/projectdiscovery/wappalyzergo v0.0.112
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/seh-msft/burpxml v1.0.1
+ github.com/stretchr/testify v1.9.0
github.com/zmap/zgrab2 v0.1.8-0.20230806160807-97ba87c0e706
- golang.org/x/term v0.16.0
+ golang.org/x/term v0.17.0
gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v3 v3.0.1
+ moul.io/http2curl v1.0.0
)
require (
@@ -123,15 +128,15 @@ require (
github.com/bits-and-blooms/bloom/v3 v3.5.0 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
+ github.com/cespare/xxhash v1.1.0 // 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.7 // indirect
github.com/containerd/continuity v0.4.2 // indirect
- github.com/corpix/uarand v0.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
- github.com/denisbrodbeck/machineid v1.0.1 // indirect
+ github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/docker/cli v24.0.5+incompatible // indirect
@@ -144,6 +149,9 @@ require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
+ github.com/go-fed/httpsig v1.1.0 // indirect
+ github.com/go-openapi/jsonpointer v0.20.2 // indirect
+ github.com/go-openapi/swag v0.22.9 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
@@ -153,46 +161,52 @@ require (
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-uuid v1.0.3 // 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/invopop/yaml v0.2.0 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
- github.com/jcmturner/gofork v1.0.0 // indirect
- github.com/jcmturner/rpc/v2 v2.0.2 // indirect
+ github.com/jcmturner/gofork v1.7.6 // indirect
+ github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
github.com/kataras/jwt v0.1.10 // indirect
- github.com/klauspost/compress v1.17.4 // indirect
- github.com/klauspost/pgzip v1.2.5 // indirect
+ github.com/klauspost/compress v1.17.6 // indirect
+ github.com/klauspost/pgzip v1.2.6 // 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/mailru/easyjson v0.7.7 // indirect
github.com/mholt/archiver/v3 v3.5.1 // indirect
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // 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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // 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.12 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
- github.com/pierrec/lz4/v4 v4.1.2 // indirect
+ github.com/perimeterx/marshmallow v1.1.5 // indirect
+ github.com/pierrec/lz4/v4 v4.1.21 // 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.6 // indirect
+ github.com/projectdiscovery/asnmap v1.1.0 // indirect
github.com/projectdiscovery/cdncheck v1.0.9 // indirect
github.com/projectdiscovery/freeport v0.0.5 // indirect
+ github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // indirect
+ github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 // indirect
github.com/projectdiscovery/stringsutil v0.0.2 // indirect
github.com/quic-go/quic-go v0.40.1 // indirect
github.com/refraction-networking/utls v1.6.1 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
- github.com/smartystreets/assertions v1.0.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/tidwall/buntdb v1.3.0 // indirect
github.com/tidwall/gjson v1.17.0 // indirect
@@ -220,7 +234,6 @@ require (
require (
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect
- github.com/Mzack9999/ldapserver v1.0.2-0.20211229000134-b44a0d6ad0dd // indirect
github.com/PuerkitoBio/goquery v1.8.1 // indirect
github.com/akrylysov/pogreb v0.10.2 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
@@ -256,8 +269,8 @@ require (
github.com/libdns/libdns v0.2.1 // indirect
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
- github.com/mattn/go-isatty v0.0.19 // indirect
- github.com/mattn/go-runewidth v0.0.14 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mholt/acmez v1.2.0 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -265,14 +278,13 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/projectdiscovery/blackrock v0.0.1 // indirect
- github.com/projectdiscovery/networkpolicy v0.0.7
- github.com/rivo/uniseg v0.4.4 // indirect
+ github.com/projectdiscovery/networkpolicy v0.0.8
+ github.com/rivo/uniseg v0.4.6 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/trivago/tgo v1.0.7
github.com/ulikunitz/xz v0.5.11 // indirect
- github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/yl2chen/cidranger v1.0.2 // indirect
github.com/ysmood/goob v0.4.0 // indirect
@@ -284,21 +296,21 @@ require (
go.etcd.io/bbolt v1.3.8 // indirect
go.uber.org/zap v1.25.0 // indirect
goftp.io/server/v2 v2.0.1 // indirect
- golang.org/x/crypto v0.18.0 // indirect
- golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3
+ golang.org/x/crypto v0.19.0 // indirect
+ golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
golang.org/x/mod v0.14.0 // indirect
- golang.org/x/sys v0.16.0 // indirect
+ golang.org/x/sys v0.17.0 // indirect
golang.org/x/time v0.3.0 // indirect
- golang.org/x/tools v0.17.0 // indirect
+ golang.org/x/tools v0.17.0
google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/protobuf v1.31.0 // indirect
+ google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
)
require (
github.com/Microsoft/go-winio v0.6.1 // indirect
- github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
+ github.com/ProtonMail/go-crypto v1.1.0-alpha.0-proton // indirect
github.com/alecthomas/chroma v0.10.0
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect
@@ -316,6 +328,7 @@ require (
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+ github.com/jcmturner/gokrb5/v8 v8.4.4
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
diff --git a/go.sum b/go.sum
index 9fc7f5053d..0233b2094e 100644
--- a/go.sum
+++ b/go.sum
@@ -33,6 +33,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
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=
+code.gitea.io/sdk/gitea v0.17.0 h1:8JPBss4+Jf7AE1YcfyiGrngTXE8dFSG3si/bypsTH34=
+code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
@@ -72,13 +74,13 @@ github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 h1:KFac3SiGbId8ub
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/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
-github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
-github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
+github.com/ProtonMail/go-crypto v1.1.0-alpha.0-proton h1:P5Wd8eQ6zAzT4HpJI67FDKnTSf3xiJGQFqY1agDJPy4=
+github.com/ProtonMail/go-crypto v1.1.0-alpha.0-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
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=
@@ -198,7 +200,6 @@ github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbA
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/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
@@ -209,6 +210,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH
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 v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
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=
@@ -226,12 +229,13 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
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/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
+github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
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/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
@@ -244,8 +248,6 @@ github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ
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=
@@ -256,8 +258,8 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG
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/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
+github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
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=
@@ -328,6 +330,8 @@ github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
+github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8=
+github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
github.com/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=
@@ -338,6 +342,8 @@ 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-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
+github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
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.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
@@ -365,6 +371,10 @@ 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-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
+github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
+github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE=
+github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE=
github.com/go-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=
@@ -385,6 +395,8 @@ github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
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/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
+github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
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=
@@ -504,7 +516,7 @@ github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A
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/sessions v1.2.1/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=
@@ -529,9 +541,11 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
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-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
+github.com/hashicorp/go-uuid v1.0.3/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.5.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=
@@ -553,10 +567,13 @@ github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439Z
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
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.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
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/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
+github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/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=
@@ -569,12 +586,14 @@ github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFK
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/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
+github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
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/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
+github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
+github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
+github.com/jcmturner/rpc/v2 v2.0.3/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=
@@ -585,6 +604,8 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
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/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
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=
@@ -610,15 +631,16 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
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.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
-github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
+github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
+github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
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/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
+github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
+github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
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=
@@ -640,6 +662,8 @@ 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/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa h1:KQKuQDgA3DZX6C396lt3WDYB9Um1gLITLbvficVbqXk=
+github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa/go.mod h1:HbwNE4XGwjgtUELkvQaAOjWrpianHYZdQVNqSdYW3UM=
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=
@@ -657,6 +681,8 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2
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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
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=
@@ -665,13 +691,16 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
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-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/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/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
@@ -712,6 +741,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/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=
@@ -771,12 +802,15 @@ github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtb
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
+github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
+github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
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/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
+github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
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=
@@ -794,54 +828,58 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
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.6 h1:NZj1hybBf4KF/hMCgJ6E2GXCe60tg5fIRkexEIU+0og=
-github.com/projectdiscovery/asnmap v1.0.6/go.mod h1:cXQjWMgxkl+8A4861Nms9u+ASxQLTb47imJD+AyX+dU=
+github.com/projectdiscovery/asnmap v1.1.0 h1:ynvbLB5cNpyQ2+k9IP0Rpla+0JmCJpd3mw6KLAW13m0=
+github.com/projectdiscovery/asnmap v1.1.0/go.mod h1:QNjBnGLxUBEZAgaYk/Av5cjKKWFY3i/FOfoIWCUApoY=
github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ=
github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
github.com/projectdiscovery/cdncheck v1.0.9 h1:BS15gzj9gb5AVSKqTDzPamfSgStu7nJQOocUvrssFlg=
github.com/projectdiscovery/cdncheck v1.0.9/go.mod h1:18SSl1w7rMj53CGeRIZTbDoa286a6xZIxGbaiEo4Fxs=
github.com/projectdiscovery/clistats v0.0.20 h1:5jO5SLiRJ7f0nDV0ndBNmBeesbROouPooH+DGMgoWq4=
github.com/projectdiscovery/clistats v0.0.20/go.mod h1:GJ2av0KnOvK0AISQnP8hyDclYIji1LVkx2l0pwnzAu4=
-github.com/projectdiscovery/dsl v0.0.41 h1:lAn+W/Lu6q0xvWJ3QhxUiKWg+p3LL/kALeoZJm23Wk8=
-github.com/projectdiscovery/dsl v0.0.41/go.mod h1:p1FYAyqoiC9eWYoJDQuqARETv6OCYVmZOqOD+e3RPJE=
-github.com/projectdiscovery/fastdialer v0.0.57 h1:4k/JuioxwbPOp3TKejm1lZlgTvXKu1IKlqQP3WAk65A=
-github.com/projectdiscovery/fastdialer v0.0.57/go.mod h1:Ah8GVwZr8X+0EQwMI66yMLTpS5QrDStc68tGtvPEaEw=
+github.com/projectdiscovery/dsl v0.0.46 h1:zBNNzSBA1aakGY44w6KhnjJQx/zO+oW2Wx7TR8xZm/A=
+github.com/projectdiscovery/dsl v0.0.46/go.mod h1:eoZEJFCIT5emI00xj8HTQwHz4YwwGAD+grL3G7CDlfs=
+github.com/projectdiscovery/fastdialer v0.0.62 h1:Wyba2hD6ZF3S04MgCn380mC+1RXJ+dq14Yq8u2yk7ps=
+github.com/projectdiscovery/fastdialer v0.0.62/go.mod h1:2baj2TRXTw+hHbKTW9IZR4dhpxCGJkq5AKL1ge5gis8=
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvmyw5pFTHS8Q=
github.com/projectdiscovery/freeport v0.0.5/go.mod h1:PY0bxSJ34HVy67LHIeF3uIutiCSDwOqKD8ruBkdiCwE=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG906Drtbpz4DwU5mhGIeOhRcktDH4cGQitGUMAsg=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=
-github.com/projectdiscovery/goflags v0.1.37 h1:R/8HLSLlFgShKKn8BO/uHTdnTq7D1igqszgTzK5ro7s=
-github.com/projectdiscovery/goflags v0.1.37/go.mod h1:Cnm8ezMwXsEbMjAB+p2/DnVr9e4SQ3kVl6iEm7fqzoQ=
+github.com/projectdiscovery/goflags v0.1.42 h1:C3CleYUODv5Jdn4+FTCUpzm3eOXHl+GiLHbXSw5iNsQ=
+github.com/projectdiscovery/goflags v0.1.42/go.mod h1:UuS8nOpeYbVebPVWeTvcpILESC8daIwRVJ14UEt7L7c=
github.com/projectdiscovery/gologger v1.1.12 h1:uX/QkQdip4PubJjjG0+uk5DtyAi1ANPJUvpmimXqv4A=
github.com/projectdiscovery/gologger v1.1.12/go.mod h1:DI8nywPLERS5mo8QEA9E7gd5HZ3Je14SjJBH3F5/kLw=
github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=
github.com/projectdiscovery/gostruct v0.0.2/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE=
-github.com/projectdiscovery/gozero v0.0.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.36 h1:hnaiw+NZKQzoP0TXGjf5QmAtMgmrR0LHtR4xINJXWr0=
-github.com/projectdiscovery/hmap v0.0.36/go.mod h1:q6UNS9PoJqZHq5s3BjFTKdYRTZvmFr1JvhMKjNGdGgo=
-github.com/projectdiscovery/httpx v1.3.8 h1:D07kanG/AnZl4hErm4HPw3Clml+R3LZdBvfXxf6c5q8=
-github.com/projectdiscovery/httpx v1.3.8/go.mod h1:ly2a5roeXONX2nIu5xaXCus6jc/4HLVwf3JP9kSaXbQ=
-github.com/projectdiscovery/interactsh v1.1.8 h1:mDD+f/oo2tV4Z1WyUync0tgYeJyuiS89Un64Gm6Pvgk=
-github.com/projectdiscovery/interactsh v1.1.8/go.mod h1:E20ywFb7bL01GcOOk+6VZF48XZ8AZvYvBpULoBUSTbg=
+github.com/projectdiscovery/gozero v0.0.2 h1:8fJeaCjxL9tpm33uG/RsCQs6HGM/NE6eA3cjkilRQ+E=
+github.com/projectdiscovery/gozero v0.0.2/go.mod h1:d8bZvDWW07LWNYWrwjZ4OO1I0cpkfqaysyDfSs9ibK8=
+github.com/projectdiscovery/hmap v0.0.41 h1:8IgTyDce3/2JzcfPVA4H+XpBRFfETULx8td3BMdSYVE=
+github.com/projectdiscovery/hmap v0.0.41/go.mod h1:bCrai6x5Eijqm2U+jtcH0wZX5ZcaZhcvzoMGTZgLAf0=
+github.com/projectdiscovery/httpx v1.6.0 h1:6g4UoSQpsOyZgaK+SMLLnZIAU0eYyTxBUwVl+jtm0JQ=
+github.com/projectdiscovery/httpx v1.6.0/go.mod h1:dzMzOWKfeofaXcXzac3O+YmuY24P0CRnviKGxvol6MM=
+github.com/projectdiscovery/interactsh v1.1.9 h1:b77SaSGrO+DtivmWwqGGY2dmNlQC3Zgmwlaj9L4Oqvc=
+github.com/projectdiscovery/interactsh v1.1.9/go.mod h1:0FRQXCildcTLq7Tsa4BVZAsFCXhpWs4C4quKWigXb5I=
+github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=
+github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb/go.mod h1:vmgC0DTFCfoCLp0RAfsfYTZZan0QMVs+cmTbH6blfjk=
+github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 h1:ZScLodGSezQVwsQDtBSMFp72WDq0nNN+KE/5DHKY5QE=
+github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI=
github.com/projectdiscovery/mapcidr v1.1.16 h1:rjj1w5D6hbTsUQXYClLcGdfBEy9bryclgi70t0vBggo=
github.com/projectdiscovery/mapcidr v1.1.16/go.mod h1:rGqpBhStdwOQ2uS62QM9qPsybwMwIhT7CTd2bxoHs8Q=
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.7 h1:AwHqBRXBqDQgnWzBMuoJtHBNEYBw+NFp/4qIK688x7o=
-github.com/projectdiscovery/networkpolicy v0.0.7/go.mod h1:CK0CnFoLF1Nou6mY7P4WODSAxhPN8g8g7XpapgEP8tI=
-github.com/projectdiscovery/ratelimit v0.0.26 h1:sxZCh72lMpQ1YNnJOWrJ+uZE9GFWdVE58LOArOc6c+4=
-github.com/projectdiscovery/ratelimit v0.0.26/go.mod h1:2NHqfqqb9xAnqW+Ztd8AzzNi+JP38Kcdhb8cnbfX9sI=
-github.com/projectdiscovery/rawhttp v0.1.35 h1:9Hkbu1WLN5coj6+HBaqi26PjMNFnw1XrMvJUS/G40OM=
-github.com/projectdiscovery/rawhttp v0.1.35/go.mod h1:9mS0N3BfOBYwQWgyI+bXBaFVMFBtJVTcZF0FENea7mA=
+github.com/projectdiscovery/networkpolicy v0.0.8 h1:XvfBaBwSDNTesSfNQP9VLk3HX9I7x7gHm028TJ5XwI8=
+github.com/projectdiscovery/networkpolicy v0.0.8/go.mod h1:xnjNqhemxUPxU+UD5Jgsc3+K8IVmcqT1SJeo6UzMtkI=
+github.com/projectdiscovery/ratelimit v0.0.27 h1:McTgnl8CtaEPmPtb9JG7EfgaQ1Rhu0pHa0Kf5Kld6Xs=
+github.com/projectdiscovery/ratelimit v0.0.27/go.mod h1:5suG3x1d5+UV4xe2RBE/QCvQkz8CaxPvdwztjab3GzM=
+github.com/projectdiscovery/rawhttp v0.1.40 h1:HSWABDP2gSeyYCskQbcjVXrtBD9aiOPf+qZiemQKlXo=
+github.com/projectdiscovery/rawhttp v0.1.40/go.mod h1:mvX/3WTDta3hvAd4BqJT7muGAqYzAocoDeUgBN2aDLc=
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917 h1:m03X4gBVSorSzvmm0bFa7gDV4QNSOWPL/fgZ4kTXBxk=
github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917/go.mod h1:JxXtZC9e195awe7EynrcnBJmFoad/BNDzW9mzFkK8Sg=
-github.com/projectdiscovery/retryabledns v1.0.53 h1:eX4f7Afi2INmEaDj8F5x5T/VfEj62Q5qS+z3kcRuMBk=
-github.com/projectdiscovery/retryabledns v1.0.53/go.mod h1:FbjRnVnTkyGujjnQGDVzMomIYA4apN+AR6VrlJ8toHk=
-github.com/projectdiscovery/retryablehttp-go v1.0.44 h1:hicCe2h6daHt4muPovmffZE3YKBqGioreO6EpIGZ87g=
-github.com/projectdiscovery/retryablehttp-go v1.0.44/go.mod h1:7ECXK2cH2/G4sstf8hacyrMdPPJ/3wCAO5tFPZ4iO4s=
+github.com/projectdiscovery/retryabledns v1.0.58 h1:ut1FSB9+GZ6zQIlKJFLqIz2RZs81EmkbsHTuIrWfYLE=
+github.com/projectdiscovery/retryabledns v1.0.58/go.mod h1:RobmKoNBgngAVE4H9REQtaLP1pa4TCyypHy1MWHT1mY=
+github.com/projectdiscovery/retryablehttp-go v1.0.51 h1:8XMrNC8JrwvySESe2d+XWF9bq4unWqD4PUPEC4Cai8s=
+github.com/projectdiscovery/retryablehttp-go v1.0.51/go.mod h1:6cdh/acYHpeYWg7+Iblh4xBRb87bC118L4G4mpvCMuA=
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=
@@ -850,12 +888,12 @@ github.com/projectdiscovery/tlsx v1.1.6 h1:iw2zwKbd2+kRQ8J1G4dLmS0CLyemd/tKz1Uzc
github.com/projectdiscovery/tlsx v1.1.6/go.mod h1:s7SRRFdrwIZBK/RXXZi4CR/CubqFSvp8h5Bk1srEZIo=
github.com/projectdiscovery/uncover v1.0.7 h1:ut+2lTuvmftmveqF5RTjMWAgyLj8ltPQC7siFy9sj0A=
github.com/projectdiscovery/uncover v1.0.7/go.mod h1:HFXgm1sRPuoN0D4oATljPIdmbo/EEh1wVuxQqo/dwFE=
-github.com/projectdiscovery/useragent v0.0.35 h1:DeOOHoBSMLQdFD8mqb5oss+OHshCPx31cDlt2/uoc5k=
-github.com/projectdiscovery/useragent v0.0.35/go.mod h1:6SJxoll5xe9PFw2zw/dN2hpgE11nv41uUR6eKzmNUEU=
-github.com/projectdiscovery/utils v0.0.76 h1:6azn0Zju0taw5Y9qAjpGPxyqwJf2AI4VJjtIzPBcRzQ=
-github.com/projectdiscovery/utils v0.0.76/go.mod h1:ERIYcW+h5jKIYyYkfdOpNPIUtH8Ogz4q5Wq3gx/71Zw=
-github.com/projectdiscovery/wappalyzergo v0.0.109 h1:BERfwTRn1dvB1tbhyc5m67R8VkC9zbVuPsEq4VEm07k=
-github.com/projectdiscovery/wappalyzergo v0.0.109/go.mod h1:4Z3DKhi75zIPMuA+qSDDWxZvnhL4qTLmDx4dxNMu7MA=
+github.com/projectdiscovery/useragent v0.0.40 h1:1LUhReSGPkhqsM5n40OOC9dIoNqMGs1dyGFJcOmg2Fo=
+github.com/projectdiscovery/useragent v0.0.40/go.mod h1:EvK1x3s948Gtqb/XOahXcauyejCL/rSgy5d1IAvsKT4=
+github.com/projectdiscovery/utils v0.0.84-0.20240312214300-d3ba70dbb9ca h1:GY9lUYDlENXPSFPJH01Bm1BfhrUF2jpnUBR+K4VPJIs=
+github.com/projectdiscovery/utils v0.0.84-0.20240312214300-d3ba70dbb9ca/go.mod h1:wzMfHBq2I9oy+DEiMfUYV86g1D7eXKaQsgWnqFpmMtI=
+github.com/projectdiscovery/wappalyzergo v0.0.112 h1:QPpp5jmj1lqLd5mFdFKQ9VvcYhQNqyU9Mr+IB0US2zA=
+github.com/projectdiscovery/wappalyzergo v0.0.112/go.mod h1:hc/o+fgM8KtdpFesjfBTmHTwsR+yBd+4kYZW/DGy/x8=
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=
@@ -901,15 +939,13 @@ github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
-github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
+github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
-github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
-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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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=
@@ -923,6 +959,8 @@ github.com/sashabaranov/go-openai v1.15.3/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adO
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/seh-msft/burpxml v1.0.1 h1:5G3QPSzvfA1WcX7LkxmKBmK2RnNyGviGWnJPumE0nwg=
+github.com/seh-msft/burpxml v1.0.1/go.mod h1:lTViCHPtGGS0scK0B4krm6Ld1kVZLWzQccwUomRc58I=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@@ -943,14 +981,14 @@ 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.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
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/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 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
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=
@@ -977,8 +1015,9 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
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.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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=
@@ -1022,8 +1061,6 @@ github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oW
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
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=
@@ -1139,22 +1176,23 @@ golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8U
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-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=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
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.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
-golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
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=
@@ -1165,8 +1203,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
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-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
-golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
+golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
+golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
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=
@@ -1240,7 +1278,6 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
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.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -1248,8 +1285,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
-golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
-golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
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=
@@ -1351,13 +1388,12 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
@@ -1365,8 +1401,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
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.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
-golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
-golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
+golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
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=
@@ -1376,7 +1412,6 @@ 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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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=
@@ -1536,8 +1571,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1577,6 +1612,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
diff --git a/integration_tests/fuzz/fuzz-body-generic-sqli.yaml b/integration_tests/fuzz/fuzz-body-generic-sqli.yaml
new file mode 100644
index 0000000000..83ca496220
--- /dev/null
+++ b/integration_tests/fuzz/fuzz-body-generic-sqli.yaml
@@ -0,0 +1,39 @@
+id: fuzz-body-generic
+
+info:
+ name: fuzzing error sqli payloads in http req body
+ author: pdteam
+ severity: info
+ description: |
+ This template attempts to find SQL injection vulnerabilities by fuzzing http body
+ It automatically handles and parses json,xml,multipart form and x-www-form-urlencoded data
+ and performs fuzzing on the value of every key
+
+http:
+ - filters:
+ - type: dsl
+ dsl:
+ - method != "GET"
+ - method != "HEAD"
+ - contains(path, "/user") # for scope of integration test
+ condition: and
+
+ payloads:
+ injection:
+ - "'"
+ - "\""
+ - ";"
+
+ fuzzing:
+ - part: body
+ type: postfix
+ mode: single
+ fuzz:
+ - '{{injection}}'
+
+ stop-at-first-match: true
+ matchers:
+ - type: word
+ words:
+ - "unrecognized token:"
+ - "null"
diff --git a/integration_tests/fuzz/fuzz-body-json-sqli.yaml b/integration_tests/fuzz/fuzz-body-json-sqli.yaml
new file mode 100644
index 0000000000..187ce1b46c
--- /dev/null
+++ b/integration_tests/fuzz/fuzz-body-json-sqli.yaml
@@ -0,0 +1,40 @@
+id: json-body-error-sqli
+
+info:
+ name: fuzzing error sqli payloads in json body
+ author: pdteam
+ severity: info
+ description: |
+ This template attempts to find SQL injection vulnerabilities by fuzzing http body of json type.
+ This is achieved by performing [ruleType](example: postfix) on value of json key
+ Note: this is example template, and payloads/matchers need to be modified appropriately.
+
+http:
+ - filters:
+ - type: dsl
+ dsl:
+ - method != "GET"
+ - method != "HEAD"
+ - contains(content_type, "application/json")
+ - contains(path, "/user") # for scope of integration test
+ condition: and
+
+ payloads:
+ injection:
+ - "'"
+ - "\""
+ - ";"
+
+ fuzzing:
+ - part: body
+ type: postfix
+ mode: single
+ fuzz:
+ - '{{injection}}'
+
+ stop-at-first-match: true
+ matchers:
+ - type: word
+ words:
+ - "unrecognized token:"
+ - "null"
diff --git a/integration_tests/fuzz/fuzz-body-multipart-form-sqli.yaml b/integration_tests/fuzz/fuzz-body-multipart-form-sqli.yaml
new file mode 100644
index 0000000000..dcca2e18d5
--- /dev/null
+++ b/integration_tests/fuzz/fuzz-body-multipart-form-sqli.yaml
@@ -0,0 +1,41 @@
+id: body-multipart-error-sqli
+
+info:
+ name: fuzzing error sqli payloads in body of multipart form data
+ author: pdteam
+ severity: info
+ description: |
+ This template attempts to find SQL injection vulnerabilities by fuzzing http body of multipart form data (file upload, etc.)
+ This is achieved by performing [ruleType](example: postfix) on value of body form key
+ Note: this is example template, and payloads/matchers need to be modified appropriately.
+
+http:
+ - filters:
+ - type: dsl
+ dsl:
+ - method != "GET"
+ - method != "HEAD"
+ - contains(content_type, "multipart/form-data")
+ - contains(path, "/user") # for scope of integration test
+ condition: and
+
+ payloads:
+ injection:
+ - "'"
+ - "\""
+ - ";"
+
+ fuzzing:
+ - part: body
+ type: postfix
+ mode: single
+ fuzz:
+ - '{{injection}}'
+
+ stop-at-first-match: true
+ matchers:
+ - type: word
+ words:
+ - "unrecognized token:"
+ - "null"
+ - "SELECTs to the left and right of UNION do not have the same number of result columns"
diff --git a/integration_tests/fuzz/fuzz-body-params-sqli.yaml b/integration_tests/fuzz/fuzz-body-params-sqli.yaml
new file mode 100644
index 0000000000..2fdd174261
--- /dev/null
+++ b/integration_tests/fuzz/fuzz-body-params-sqli.yaml
@@ -0,0 +1,41 @@
+id: body-params-error-sqli
+
+info:
+ name: fuzzing error sqli payloads in body with params
+ author: pdteam
+ severity: info
+ description: |
+ This template attempts to find SQL injection vulnerabilities by fuzzing http body of x-www-form-urlencoded data
+ This is achieved by performing [ruleType](example: postfix) on value of body key
+ Note: this is example template, and payloads/matchers need to be modified appropriately.
+
+http:
+ - filters:
+ - type: dsl
+ dsl:
+ - method != "GET"
+ - method != "HEAD"
+ - contains(content_type, "application/x-www-form-urlencoded")
+ - contains(path, "/user") # for scope of integration test
+ condition: and
+
+ payloads:
+ injection:
+ - "'"
+ - "\""
+ - ";"
+
+ fuzzing:
+ - part: body
+ type: postfix
+ mode: single
+ fuzz:
+ - '{{injection}}'
+
+ stop-at-first-match: true
+ matchers:
+ - type: word
+ words:
+ - "unrecognized token:"
+ - "null"
+ - "SELECTs to the left and right of UNION do not have the same number of result columns"
diff --git a/integration_tests/fuzz/fuzz-body-xml-sqli.yaml b/integration_tests/fuzz/fuzz-body-xml-sqli.yaml
new file mode 100644
index 0000000000..8ac62842d5
--- /dev/null
+++ b/integration_tests/fuzz/fuzz-body-xml-sqli.yaml
@@ -0,0 +1,40 @@
+id: xml-body-error-sqli
+
+info:
+ name: fuzzing error sqli payloads in xml body
+ author: pdteam
+ severity: info
+ description: |
+ This template attempts to find SQL injection vulnerabilities by fuzzing http body of xml type.
+ This is achieved by performing [ruleType](example: postfix) on value of xml key
+ Note: this is example template, and payloads/matchers need to be modified appropriately.
+
+http:
+ - filters:
+ - type: dsl
+ dsl:
+ - method != "GET"
+ - method != "HEAD"
+ - contains(content_type, "application/xml")
+ - contains(path, "/user") # for scope of integration test
+ condition: and
+
+ payloads:
+ injection:
+ - "'"
+ - "\""
+ - ";"
+
+ fuzzing:
+ - part: body
+ type: postfix
+ mode: single
+ fuzz:
+ - '{{injection}}'
+
+ stop-at-first-match: true
+ matchers:
+ - type: word
+ words:
+ - "unrecognized token:"
+ - "null"
diff --git a/integration_tests/fuzz/fuzz-cookie-error-sqli.yaml b/integration_tests/fuzz/fuzz-cookie-error-sqli.yaml
new file mode 100644
index 0000000000..86bb2a1639
--- /dev/null
+++ b/integration_tests/fuzz/fuzz-cookie-error-sqli.yaml
@@ -0,0 +1,59 @@
+id: cookie-fuzzing-error-sqli
+
+info:
+ name: fuzzing error sqli payloads in cookie
+ author: pdteam
+ severity: info
+ description: |
+ This template attempts to find SQL injection vulnerabilities by fuzzing http cookies with SQL injection payloads.
+ Note: this is example template, and payloads/matchers need to be modified appropriately.
+
+http:
+ - filters:
+ - type: dsl
+ dsl:
+ - 'method == "GET"'
+ - len(cookie) > 0
+ condition: and
+
+ payloads:
+ sqli:
+ - "'"
+ - ''
+ - '`'
+ - '``'
+ - ','
+ - '"'
+ - ""
+ - /
+ - //
+ - \
+ - \\
+ - ;
+ - -- or #
+ - '" OR 1 = 1 -- -'
+ - ' OR '' = '
+ - '='
+ - 'LIKE'
+ - "'=0--+"
+ - OR 1=1
+ - "' OR 'x'='x"
+ - "' AND id IS NULL; --"
+ - "'''''''''''''UNION SELECT '2"
+ - '%00'
+
+ fuzzing:
+ - part: cookie
+ type: postfix
+ mode: single
+ fuzz:
+ - '{{sqli}}'
+
+ stop-at-first-match: true
+ matchers:
+ - type: word
+ words:
+ - "unrecognized token:"
+ - "syntax error"
+ - "null"
+ - "SELECTs to the left and right of UNION do not have the same number of result columns"
diff --git a/integration_tests/fuzz/fuzz-header-basic.yaml b/integration_tests/fuzz/fuzz-header-basic.yaml
index 1441878a37..10d2928c32 100644
--- a/integration_tests/fuzz/fuzz-header-basic.yaml
+++ b/integration_tests/fuzz/fuzz-header-basic.yaml
@@ -28,7 +28,7 @@ http:
- "'\"><{{first}}"
fuzzing:
- - part: headers
+ - part: header
type: replace
mode: single
keys: ["Origin"]
diff --git a/integration_tests/fuzz/fuzz-header-multiple.yaml b/integration_tests/fuzz/fuzz-header-multiple.yaml
index 04b88b1ff6..0a535b5776 100644
--- a/integration_tests/fuzz/fuzz-header-multiple.yaml
+++ b/integration_tests/fuzz/fuzz-header-multiple.yaml
@@ -25,7 +25,7 @@ http:
- "secret.local"
fuzzing:
- - part: headers
+ - part: header
type: replace
mode: multiple
keys: ["Origin", "X-Forwared-For"]
diff --git a/integration_tests/fuzz/fuzz-host-header-injection.yaml b/integration_tests/fuzz/fuzz-host-header-injection.yaml
new file mode 100644
index 0000000000..cda22235e1
--- /dev/null
+++ b/integration_tests/fuzz/fuzz-host-header-injection.yaml
@@ -0,0 +1,43 @@
+id: host-header-injection
+
+info:
+ name: Host Header Injection
+ author: pdteam
+ severity: info
+ description: Host header injection
+
+variables:
+ domain: "oast.fun"
+
+http:
+ - filters:
+ - type: dsl
+ dsl:
+ - 'method == "GET"'
+ - 'contains(path,"/host-header-lab")' # for integration testing only
+ condition: and
+
+ fuzzing:
+ - part: header
+ type: replace
+ mode: single
+ fuzz:
+ X-Forwarded-For: "{{domain}}"
+ X-Forwarded-Host: "{{domain}}"
+ Forwarded: "{{domain}}"
+ X-Real-IP: "{{domain}}"
+ X-Original-URL: "{{domain}}"
+ X-Rewrite-URL: "{{domain}}"
+ Host: "{{domain}}"
+ # " Host": "{{domain}}" # space before host (not supported yet due to lack of unsafe mode)
+
+ matchers:
+ - type: status
+ status:
+ - 200
+
+ - type: word
+ part: body
+ words:
+ - "Interactsh"
+ matchers-condition: and
\ No newline at end of file
diff --git a/integration_tests/fuzz/fuzz-mode.yaml b/integration_tests/fuzz/fuzz-mode.yaml
index d866408396..549eb54e51 100644
--- a/integration_tests/fuzz/fuzz-mode.yaml
+++ b/integration_tests/fuzz/fuzz-mode.yaml
@@ -5,7 +5,7 @@ info:
author: pdteam
severity: info
-requests:
+http:
- method: GET
path:
- "{{BaseURL}}"
diff --git a/integration_tests/fuzz/fuzz-path-sqli.yaml b/integration_tests/fuzz/fuzz-path-sqli.yaml
new file mode 100644
index 0000000000..e034dec3ff
--- /dev/null
+++ b/integration_tests/fuzz/fuzz-path-sqli.yaml
@@ -0,0 +1,42 @@
+id: path-based-sqli
+
+info:
+ name: Path Based SQLi
+ author: pdteam
+ severity: info
+ description: |
+ This template attempts to find SQL injection vulnerabilities on path based sqli and replacing numerical values with fuzzing payloads.
+ ex: /admin/user/55/profile , /user/15/action/update, /posts/15, /blog/100/data, /page/51/ etc these types of paths are filtered and
+ replaced with sqli path payloads.
+ Note: this is example template, and payloads/matchers need to be modified appropriately.
+
+http:
+ - filters:
+ - type: dsl
+ dsl:
+ - 'method == "GET"'
+ - regex("/(.*?/)([0-9]+)(/.*)?",path)
+ condition: and
+
+ payloads:
+ pathsqli:
+ - "'OR1=1"
+ - '%20OR%20True'
+
+ fuzzing:
+ - part: path
+ type: replace-regex
+ mode: single
+ replace-regex: '/(.*?/)([0-9]+)(/.*)?'
+ fuzz:
+ - '/${1}${2}{{pathsqli}}${3}'
+
+ matchers:
+ - type: status
+ status:
+ - 200
+
+ - type: word
+ words:
+ - "admin"
+ matchers-condition: and
\ No newline at end of file
diff --git a/integration_tests/fuzz/fuzz-query-num-replace.yaml b/integration_tests/fuzz/fuzz-query-num-replace.yaml
new file mode 100644
index 0000000000..90f3a3934f
--- /dev/null
+++ b/integration_tests/fuzz/fuzz-query-num-replace.yaml
@@ -0,0 +1,39 @@
+id: fuzz-query-num
+
+info:
+ name: Fuzz Query Param For IDOR
+ author: pdteam
+ severity: info
+ description: Query Value Fuzzing using Fuzzing Rules
+
+http:
+ - filters:
+ - type: dsl
+ dsl:
+ - 'len(query) > 0'
+ # below filter is related to integration testing
+ - type: word
+ part: path
+ words:
+ - /blog/post
+ filters-condition: and
+
+ payloads:
+ nums:
+ - 200
+ - 201
+
+ fuzzing:
+ - part: query
+ type: replace
+ mode: multiple
+ values:
+ - "^[0-9]+$" # only if value is number
+ fuzz:
+ - '{{nums}}'
+
+ matchers:
+ - type: status
+ status:
+ - 200
+
diff --git a/integration_tests/fuzz/fuzz-query.yaml b/integration_tests/fuzz/fuzz-query.yaml
index 3a0b672ec8..658ee5536c 100644
--- a/integration_tests/fuzz/fuzz-query.yaml
+++ b/integration_tests/fuzz/fuzz-query.yaml
@@ -5,7 +5,7 @@ info:
author: pdteam
severity: info
-requests:
+http:
- method: GET
path:
- "{{BaseURL}}"
diff --git a/integration_tests/fuzz/fuzz-type.yaml b/integration_tests/fuzz/fuzz-type.yaml
index 860432c312..e2c20556f4 100644
--- a/integration_tests/fuzz/fuzz-type.yaml
+++ b/integration_tests/fuzz/fuzz-type.yaml
@@ -5,7 +5,7 @@ info:
author: pdteam
severity: info
-requests:
+http:
- method: GET
path:
- "{{BaseURL}}"
diff --git a/integration_tests/fuzz/testData/ginandjuice.proxify.yaml b/integration_tests/fuzz/testData/ginandjuice.proxify.yaml
new file mode 100644
index 0000000000..734ea39cb0
--- /dev/null
+++ b/integration_tests/fuzz/testData/ginandjuice.proxify.yaml
@@ -0,0 +1,535 @@
+timestamp: 2024-02-20T19:24:13+05:30
+url: http://127.0.0.1:8082/blog/post?postId=3&source=proxify
+request:
+ header:
+ Accept-Encoding: gzip
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: 127.0.0.1:8082
+ method: GET
+ path: /blog/post
+ scheme: https
+ raw: |+
+ GET /blog/post?postId=3&source=proxify HTTP/1.1
+ Host: 127.0.0.1:8082
+ Accept-Encoding: gzip
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+
+response:
+ header:
+ Content-Encoding: gzip
+ Content-Type: text/html; charset=utf-8
+ Date: Tue, 20 Feb 2024 13:54:13 GMT
+ Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None
+ X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62
+ X-Frame-Options: SAMEORIGIN
+ raw: |+
+ HTTP/1.1 200 OK
+ Connection: close
+ Content-Encoding: gzip
+ Content-Type: text/html; charset=utf-8
+ Date: Tue, 20 Feb 2024 13:54:13 GMT
+ Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/
+ Set-Cookie: AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure
+ Set-Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None
+ X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62
+ X-Frame-Options: SAMEORIGIN
+
+---
+timestamp: 2024-02-20T19:24:13+05:30
+url: http://127.0.0.1:8082/reset-password
+request:
+ header:
+ Accept-Encoding: gzip
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: 127.0.0.1:8082
+ method: GET
+ path: /blog/post
+ scheme: https
+ raw: |+
+ POST /reset-password HTTP/1.1
+ Host: 127.0.0.1:8082
+ X-Forwarded-For: 127.0.0.1:8082
+ Accept-Encoding: gzip
+ Connection: close
+ Content-Type: application/json
+ Content-Length: 23
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+
+ {"password":"12345678"}
+response:
+ header:
+ Content-Encoding: gzip
+ Content-Type: text/html; charset=utf-8
+ Date: Tue, 20 Feb 2024 13:54:13 GMT
+ Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None
+ X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62
+ X-Frame-Options: SAMEORIGIN
+ raw: |+
+ HTTP/1.1 200 OK
+ Connection: close
+ Content-Encoding: gzip
+ Content-Type: text/html; charset=utf-8
+ Date: Tue, 20 Feb 2024 13:54:13 GMT
+ Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/
+ Set-Cookie: AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure
+ Set-Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None
+ X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62
+ X-Frame-Options: SAMEORIGIN
+
+---
+timestamp: 2024-02-20T19:24:13+06:30
+url: http://127.0.0.1:8082/user/55/profile
+request:
+ header:
+ Accept-Encoding: gzip
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: 127.0.0.1:8082
+ method: GET
+ path: /blog/post
+ scheme: https
+ raw: |+
+ GET /user/55/profile HTTP/1.1
+ Host: 127.0.0.1:8082
+ Accept-Encoding: gzip
+ Connection: close
+ Content-Type: application/json
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+
+response:
+ header:
+ Content-Type: application/json; charset=UTF-8
+ Date: Tue, 27 Feb 2024 18:46:44 GMT
+ raw: |+
+ HTTP/1.1 200 OK
+ Content-Type: application/json; charset=UTF-8
+ Date: Tue, 27 Feb 2024 18:46:44 GMT
+ Content-Length: 47
+
+ {"ID":75,"Name":"user","Age":30,"Role":"user"}
+
+---
+timestamp: 2024-02-20T23:25:13+06:30
+url: http://127.0.0.1:8082/user
+request:
+ header:
+ Accept-Encoding: gzip
+ Connection: close
+ Content-Type: application/json
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: 127.0.0.1:8082
+ method: POST
+ path: /user
+ scheme: http
+ raw: |+
+ POST /user HTTP/1.1
+ Host: localhost:8082
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ Accept: */*
+ Content-Length: 32
+ Connection: close
+ Content-Type: application/json
+
+ {"id": 7 , "name": "pdteam"}
+
+response:
+ header:
+ Content-Type: text/plain; charset=UTF-8
+ Date: Tue, 27 Feb 2024 18:46:44 GMT
+ raw: |+
+ HTTP/1.1 200 OK
+ Content-Type: text/plain; charset=UTF-8
+ Date: Wed, 28 Feb 2024 13:58:52 GMT
+ Content-Length: 25
+
+ User updated successfully
+
+---
+timestamp: 2024-02-20T23:26:13+06:30
+url: http://127.0.0.1:8082/user
+request:
+ header:
+ Accept-Encoding: gzip
+ Connection: close
+ Content-Type: application/x-www-form-urlencoded
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: 127.0.0.1:8082
+ method: POST
+ path: /user
+ scheme: http
+ raw: |+
+ POST /user HTTP/1.1
+ Host: localhost:8082
+ User-Agent: curl/8.1.2
+ Accept: */*
+ Content-Length: 20
+ Connection: close
+ Content-Type: application/x-www-form-urlencoded
+
+ id=7&name=pdteam
+
+response:
+ header:
+ Content-Type: text/plain; charset=UTF-8
+ Date: Tue, 27 Feb 2024 18:46:44 GMT
+ raw: |+
+ HTTP/1.1 200 OK
+ Content-Type: text/plain; charset=UTF-8
+ Date: Wed, 28 Feb 2024 13:58:52 GMT
+ Content-Length: 25
+
+ User updated successfully
+
+---
+timestamp: 2024-02-20T23:26:13+06:30
+url: http://127.0.0.1:8082/user
+request:
+ header:
+ Accept-Encoding: gzip
+ Connection: close
+ Content-Type: multipart/form-data
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: 127.0.0.1:8082
+ method: POST
+ path: /user
+ scheme: http
+ raw: |+
+ POST /user HTTP/1.1
+ Host: localhost:8082
+ User-Agent: curl/8.1.2
+ Accept: */*
+ Content-Length: 226
+ Connection: close
+ Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
+
+ ------WebKitFormBoundary7MA4YWxkTrZu0gW
+ Content-Disposition: form-data; name="id"
+
+ 7
+ ------WebKitFormBoundary7MA4YWxkTrZu0gW
+ Content-Disposition: form-data; name="name"
+
+ pdteam
+ ------WebKitFormBoundary7MA4YWxkTrZu0gW--
+
+response:
+ header:
+ Content-Type: text/plain; charset=UTF-8
+ Date: Tue, 27 Feb 2024 18:46:44 GMT
+ raw: |+
+ HTTP/1.1 200 OK
+ Content-Type: text/plain; charset=UTF-8
+ Date: Wed, 28 Feb 2024 13:58:52 GMT
+ Content-Length: 25
+
+ User updated successfully
+
+---
+---
+timestamp: 2024-02-20T19:25:13+06:30
+url: http://127.0.0.1:8082/blog/posts
+request:
+ header:
+ Accept-Encoding: gzip
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: 127.0.0.1:8082
+ Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; lang=en
+ method: GET
+ path: /blog/posts
+ scheme: http
+ raw: |+
+ GET /blog/posts HTTP/1.1
+ Host: 127.0.0.1:8082
+ Accept-Encoding: gzip
+ Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; lang=en
+ Connection: close
+ Content-Type: application/json
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+
+response:
+ header:
+ Content-Type: application/json; charset=UTF-8
+ Date: Tue, 27 Feb 2024 18:46:44 GMT
+ raw: |+
+ HTTP/1.1 200 OK
+ Content-Type: application/json; charset=UTF-8
+ Date: Wed, 28 Feb 2024 13:58:52 GMT
+ Content-Length: 218
+
+ [{"ID":1,"Title":"The Joy of Programming","Content":"Programming is like painting a canvas with logic.","Lang":"en"},{"ID":2,"Title":"A Journey Through Code","Content":"Every line of code tells a story.","Lang":"en"}]
+
+---
+timestamp: 2024-02-20T23:26:13+06:30
+url: http://127.0.0.1:8082/user
+request:
+ header:
+ Accept-Encoding: gzip
+ Connection: close
+ Content-Type: application/xml
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: 127.0.0.1:8082
+ method: POST
+ path: /user
+ scheme: http
+ raw: |+
+ POST /user HTTP/1.1
+ Host: localhost:8082
+ User-Agent: curl/8.1.2
+ Accept: */*
+ Content-Length: 76
+ Connection: close
+ Content-Type: application/xml
+
+
+
+ 7
+ pdteam
+
+
+response:
+ header:
+ Content-Type: text/plain; charset=UTF-8
+ Date: Tue, 27 Feb 2024 18:46:44 GMT
+ raw: |+
+ HTTP/1.1 200 OK
+ Content-Type: text/plain; charset=UTF-8
+ Date: Wed, 28 Feb 2024 13:58:52 GMT
+ Content-Length: 25
+
+ User updated successfully
+
+---
+timestamp: 2024-02-20T19:24:13+05:32
+url: http://127.0.0.1:8082/host-header-lab
+request:
+ header:
+ Accept-Encoding: gzip
+ Authorization: Bearer 3x4mpl3t0k3n
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: 127.0.0.1:8082
+ method: POST
+ path: /catalog/product
+ scheme: https
+ raw: |+
+ GET /host-header-lab HTTP/1.1
+ Host: 127.0.0.1:8082
+ Authorization: Bearer 3x4mpl3t0k3n
+ Accept-Encoding: gzip
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+
+response:
+ header:
+ Content-Encoding: gzip
+ Content-Type: text/html; charset=utf-8
+ Date: Tue, 20 Feb 2024 13:54:13 GMT
+ Set-Cookie: AWSALB=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=fFcCUjmQguQy820Y8xrnRypp3KBWSPk6; Secure; HttpOnly; SameSite=None
+ X-Backend: 2235790d-f089-4324-8ac0-f64cc96f2460
+ X-Frame-Options: SAMEORIGIN
+ body: |
+
+
+
+
+
+
+
+
+
+ Fruit Overlays - Product - Gin & Juice Shop
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Fruit Overlays
+
+
+
+ $92.79
+
+
+
+
+ Description:
+ When it comes to hospitality presentation is key, and nothing looks better than a well-dressed drink. We know gin fans like plenty of fruit in their glasses, some a bit more than they really need, but hey ho, each to their own. But what about fruit not inside your glass, but classily arranged over your glass? In comes Fruitus overlayus, the best way to jazz up any party. The possible colour combinations are endless, just picture that! All you need is a nice selection of small fruits, or maybe even use our Fruit Curliwurlier to add a dash of even more drama, and we will do the rest. This one is a real winner at our Christmas and New year’s outings, give it a go and turn some heads.
+ CONTENTS: 12 cocktail sticks.
+ HOW TO USE: Let your creative juices flow (Pun intended), and spend some time working on your colour coordination, try not to think too much about it, just do it! Pick up one of the Fruitus overlayus sticks and carefully slide the fruit along until there is a small space on either end of the stick. Balance the stick across the rim of the glass. Ta-Da! Your first fruit overlay. Keep going until you have as many overlays as you need. You can always purchase more at any time with a discount on bulk buys.
+
+
+
+
+
+
+
+
+
+
+
+ View cart
+
+
+
+
+
+
+
+
+
+
+ raw: |+
+ HTTP/1.1 200 OK
+ Connection: close
+ Content-Encoding: gzip
+ Content-Type: text/html; charset=utf-8
+ Date: Tue, 20 Feb 2024 13:54:13 GMT
+ Set-Cookie: AWSALB=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/
+ Set-Cookie: AWSALBCORS=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure
+ Set-Cookie: session=fFcCUjmQguQy820Y8xrnRypp3KBWSPk6; Secure; HttpOnly; SameSite=None
+ X-Backend: 2235790d-f089-4324-8ac0-f64cc96f2460
+ X-Frame-Options: SAMEORIGIN
\ 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
index c4b9e6c5b7..4ccf3648bf 100644
--- a/integration_tests/protocols/code/py-env-var.yaml
+++ b/integration_tests/protocols/code/py-env-var.yaml
@@ -20,4 +20,4 @@ code:
- type: word
words:
- "hello from input baz"
-# digest: 4a0a00473045022100b290a0c40f27573f0de9a950be13457a9bf59ade1ff2f497bf01a3b526e5db750220761942acffd6d27e2714ddaa1c73c699ccd7de48839f08cff1d6a9456bc8ff1f:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
+# digest: 4a0a0047304502207e3a5eda5f3207c3c01c820562243281926c1215224a7c80ed7528559b9f52cb022100f6ef99bb45843f481705778630f2cfd8f4d1cc3acb96392ff016f22e06aa91af: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
index 74203e8df3..9e0b041bcf 100644
--- a/integration_tests/protocols/code/py-file.yaml
+++ b/integration_tests/protocols/code/py-file.yaml
@@ -18,4 +18,4 @@ code:
- type: word
words:
- "hello from input"
-# digest: 490a004630440220335663a6a4db720ee6276ab7179a87a6be0b4030771ec5ee82ecf6982342113602200a2570db7eb9721f6ceb1a89543fc436ee62b30d1b720c75ea3834ed3d2b64f3:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
+# digest: 4a0a004730450220069673af9bd6d6677f9529d06f5d8bd46d543089a4731ed18ee806761d75fd60022100913a3e27b0a5809baf710ba9585bf9fe729634c0e19e3e13eef70a6bd100df34: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
index eac53c3bea..24e4b06241 100644
--- a/integration_tests/protocols/code/py-interactsh.yaml
+++ b/integration_tests/protocols/code/py-interactsh.yaml
@@ -26,4 +26,4 @@ code:
part: interactsh_protocol
words:
- "http"
-# digest: 490a004630440220400892730a62fa1bbb1064e4d88eea760dbf8f01c6b630ff0f5b126fd1952839022025a6d52e730c1f1cfcbd440e6269f93489db3a77cb2a27d0f47522c0819dc8d3:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
+# digest: 490a00463044022003b8d069e3c84412729c43e33013a52ee04eabcf096d511979691d71d8e905f60220011f4475899abed4f86b4bd5e6c2423750759135206e4729826afe1ed8a44f4d:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
diff --git a/integration_tests/protocols/code/py-snippet.yaml b/integration_tests/protocols/code/py-snippet.yaml
index f8a30b4f1f..287ca2c6eb 100644
--- a/integration_tests/protocols/code/py-snippet.yaml
+++ b/integration_tests/protocols/code/py-snippet.yaml
@@ -21,4 +21,4 @@ code:
- type: word
words:
- "hello from input"
-# digest: 490a0046304402206b14abdc0d5fc13466f5c292da9fb2a19d1b2c5e683cc052037fe367b372f82b02202c00b9acbd8106a769eb411794c567d3019433671397bf909e16b286105ed69e:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
+# digest: 4a0a00473045022100c291615cf2a8005450c17a6554e81a9cdab14743b299f0679c644247929198b502206fdacc8ab173bde2b4015340012637916bf2659f66f320fcc06b97ac639072a1:4a3eb6b4988d95847d4203be25ed1d46
\ No newline at end of file
diff --git a/integration_tests/protocols/http/http-matcher-extractor-dy-extractor.yaml b/integration_tests/protocols/http/http-matcher-extractor-dy-extractor.yaml
new file mode 100644
index 0000000000..eb26d50bf7
--- /dev/null
+++ b/integration_tests/protocols/http/http-matcher-extractor-dy-extractor.yaml
@@ -0,0 +1,36 @@
+id: http-matcher-extractor-dy-extractor
+info:
+ name: HTTP matcher and extractor & dynamic extractor
+ description: >
+ Edgecase to test for a combination of matchers , extractors and dynamic extractors
+ author: pdteam
+ severity: info
+
+http:
+ - raw:
+ - |
+ GET {{BaseURL}} HTTP/1.1
+ - |
+ GET {{absolutePath}} HTTP/1.1
+
+ req-condition: true
+ extractors:
+ - type: regex
+ internal: true
+ part: body_1
+ name: absolutePath
+ regex:
+ - ''
+ group: 1
+ - type: regex
+ internal: false
+ part: body_2
+ name: title
+ regex:
+ - ']*>([^<]+) '
+ group: 1
+ matchers:
+ - type: regex
+ part: body_2
+ regex:
+ - ']*>([^<]+) '
\ No newline at end of file
diff --git a/integration_tests/protocols/http/http-paths.yaml b/integration_tests/protocols/http/http-paths.yaml
index ae389a3793..ba5be2d502 100644
--- a/integration_tests/protocols/http/http-paths.yaml
+++ b/integration_tests/protocols/http/http-paths.yaml
@@ -28,7 +28,7 @@ info:
- "//CFIDE/wizards/common/utils.cfc"
# Test all templates with FullURLs
-requests:
+http:
- raw:
# relative path without leading slash with param
# If relative path does not have `/` prefix it is autocorrected
diff --git a/integration_tests/protocols/http/multi-http-var-sharing.yaml b/integration_tests/protocols/http/multi-http-var-sharing.yaml
new file mode 100644
index 0000000000..606a036504
--- /dev/null
+++ b/integration_tests/protocols/http/multi-http-var-sharing.yaml
@@ -0,0 +1,36 @@
+id: multi-http-var-sharing
+
+info:
+ name: Multi HTTP var sharing
+ author: pdteam
+ severity: info
+ description: |
+ A template which has multiple HTTP requests block and variables are shared between them
+
+http:
+ - method: GET
+ path:
+ - "{{BaseURL}}"
+
+ matchers:
+ - type: word
+ words:
+ - "This is test matcher text"
+ negative: true
+ internal: true
+
+ extractors:
+ - type: dsl
+ name: ffff
+ dsl:
+ - status_code
+ internal: true
+
+ - method: GET
+ path:
+ - "{{BaseURL}}/{{ffff}}"
+
+ matchers:
+ - type: status
+ status:
+ - 200
\ No newline at end of file
diff --git a/integration_tests/run.sh b/integration_tests/run.sh
index 92b76ab116..f893e1b386 100755
--- a/integration_tests/run.sh
+++ b/integration_tests/run.sh
@@ -1,7 +1,7 @@
#!/bin/bash
echo "::group::Build nuclei"
-rm integration-test nuclei 2>/dev/null
+rm integration-test fuzzplayground nuclei 2>/dev/null
cd ../cmd/nuclei
go build -race .
mv nuclei ../../integration_tests/nuclei
diff --git a/internal/runner/inputs.go b/internal/runner/inputs.go
index 406986d96a..8dc27a7a9e 100644
--- a/internal/runner/inputs.go
+++ b/internal/runner/inputs.go
@@ -8,6 +8,7 @@ import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/hmap/store/hybrid"
"github.com/projectdiscovery/httpx/common/httpx"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
stringsutil "github.com/projectdiscovery/utils/strings"
@@ -19,10 +20,15 @@ const probeBulkSize = 50
// initializeTemplatesHTTPInput initializes the http form of input
// for any loaded http templates if input is in non-standard format.
func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
+
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
if err != nil {
return nil, errors.Wrap(err, "could not create temporary input file")
}
+ if r.inputProvider.InputType() == provider.MultiFormatInputProvider {
+ // currently http probing for input mode types is not supported
+ return hm, nil
+ }
gologger.Info().Msgf("Running httpx on input host")
var bulkSize = probeBulkSize
@@ -41,7 +47,7 @@ func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {
// Probe the non-standard URLs and store them in cache
swg := sizedwaitgroup.New(bulkSize)
count := int32(0)
- r.hmapInputProvider.Scan(func(value *contextargs.MetaInput) bool {
+ r.inputProvider.Iterate(func(value *contextargs.MetaInput) bool {
if stringsutil.HasPrefixAny(value.Input, "http://", "https://") {
return true
}
diff --git a/internal/runner/lazy.go b/internal/runner/lazy.go
new file mode 100644
index 0000000000..193b22ff6c
--- /dev/null
+++ b/internal/runner/lazy.go
@@ -0,0 +1,123 @@
+package runner
+
+import (
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx"
+ "github.com/projectdiscovery/nuclei/v3/pkg/catalog"
+ "github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
+ "github.com/projectdiscovery/nuclei/v3/pkg/output"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer"
+ "github.com/projectdiscovery/nuclei/v3/pkg/scan"
+ "github.com/projectdiscovery/nuclei/v3/pkg/types"
+ errorutil "github.com/projectdiscovery/utils/errors"
+)
+
+type AuthLazyFetchOptions struct {
+ TemplateStore *loader.Store
+ ExecOpts protocols.ExecutorOptions
+ OnError func(error)
+}
+
+// GetAuthTemlStore create new loader for loading auth templates
+func GetAuthTmplStore(opts types.Options, catalog catalog.Catalog, execOpts protocols.ExecutorOptions) (*loader.Store, error) {
+ tmpls := []string{}
+ for _, file := range opts.SecretsFile {
+ data, err := authx.GetTemplatePathsFromSecretFile(file)
+ if err != nil {
+ return nil, errorutil.NewWithErr(err).Msgf("failed to get template paths from secrets file")
+ }
+ tmpls = append(tmpls, data...)
+ }
+ opts.Templates = tmpls
+ opts.Workflows = nil
+ opts.RemoteTemplateDomainList = nil
+ opts.TemplateURLs = nil
+ opts.WorkflowURLs = nil
+ opts.ExcludedTemplates = nil
+ opts.Tags = nil
+ opts.ExcludeTags = nil
+ opts.IncludeTemplates = nil
+ opts.Authors = nil
+ opts.Severities = nil
+ opts.ExcludeSeverities = nil
+ opts.IncludeTags = nil
+ opts.IncludeIds = nil
+ opts.ExcludeIds = nil
+ opts.Protocols = nil
+ opts.ExcludeProtocols = nil
+ opts.IncludeConditions = nil
+ cfg := loader.NewConfig(&opts, catalog, execOpts)
+ store, err := loader.New(cfg)
+ if err != nil {
+ return nil, errorutil.NewWithErr(err).Msgf("failed to initialize dynamic auth templates store")
+ }
+ return store, nil
+}
+
+// GetLazyAuthFetchCallback returns a lazy fetch callback for auth secrets
+func GetLazyAuthFetchCallback(opts *AuthLazyFetchOptions) authx.LazyFetchSecret {
+ return func(d *authx.Dynamic) error {
+ tmpls := opts.TemplateStore.LoadTemplates([]string{d.TemplatePath})
+ if len(tmpls) == 0 {
+ return fmt.Errorf("no templates found for path: %s", d.TemplatePath)
+ }
+ if len(tmpls) > 1 {
+ return fmt.Errorf("multiple templates found for path: %s", d.TemplatePath)
+ }
+ data := map[string]interface{}{}
+ tmpl := tmpls[0]
+ // add args to tmpl here
+ vars := map[string]interface{}{}
+ ctx := scan.NewScanContext(contextargs.NewWithInput(d.Input))
+ for _, v := range d.Variables {
+ vars[v.Key] = v.Value
+ ctx.Input.Add(v.Key, v.Value)
+ }
+
+ var finalErr error
+ ctx.OnResult = func(e *output.InternalWrappedEvent) {
+ if e == nil {
+ finalErr = fmt.Errorf("no result found for template: %s", d.TemplatePath)
+ return
+ }
+ if !e.HasOperatorResult() {
+ finalErr = fmt.Errorf("no result found for template: %s", d.TemplatePath)
+ return
+ }
+ // dynamic values
+ for k, v := range e.OperatorsResult.DynamicValues {
+ if len(v) > 0 {
+ data[k] = v[0]
+ }
+ }
+ // named extractors
+ for k, v := range e.OperatorsResult.Extracts {
+ if len(v) > 0 {
+ data[k] = v[0]
+ }
+ }
+ if len(data) == 0 {
+ if e.OperatorsResult.Matched {
+ finalErr = fmt.Errorf("match found but no (dynamic/extracted) values found for template: %s", d.TemplatePath)
+ } else {
+ finalErr = fmt.Errorf("no match or (dynamic/extracted) values found for template: %s", d.TemplatePath)
+ }
+ }
+ // log result of template in result file/screen
+ _ = writer.WriteResult(e, opts.ExecOpts.Output, opts.ExecOpts.Progress, opts.ExecOpts.IssuesClient)
+ }
+ _, err := tmpl.Executer.ExecuteWithResults(ctx)
+ if err != nil {
+ finalErr = err
+ }
+ // store extracted result in auth context
+ d.Extracted = data
+ if finalErr != nil && opts.OnError != nil {
+ opts.OnError(finalErr)
+ }
+ return finalErr
+ }
+}
diff --git a/internal/runner/options.go b/internal/runner/options.go
index 76346c927a..bc89a45bdd 100644
--- a/internal/runner/options.go
+++ b/internal/runner/options.go
@@ -289,22 +289,27 @@ func createReportingOptions(options *types.Options) (*reporting.Options, error)
// configureOutput configures the output logging levels to be displayed on the screen
func configureOutput(options *types.Options) {
- // If the user desires verbose output, show verbose output
- if options.Verbose || options.Validate {
- gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
+ // disable standard logger (ref: https://github.com/golang/go/issues/19895)
+ defer logutil.DisableDefaultLogger()
+
+ if options.NoColor {
+ gologger.DefaultLogger.SetFormatter(formatter.NewCLI(true))
}
+ // If the user desires verbose output, show verbose output
if options.Debug || options.DebugRequests || options.DebugResponse {
gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug)
}
+ // Debug takes precedence before verbose
+ // because debug is a lower logging level.
+ if options.Verbose || options.Validate {
+ gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
+ }
if options.NoColor {
gologger.DefaultLogger.SetFormatter(formatter.NewCLI(true))
}
if options.Silent {
gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
}
-
- // disable standard logger (ref: https://github.com/golang/go/issues/19895)
- logutil.DisableDefaultLogger()
}
// loadResolvers loads resolvers from both user-provided flags and file
diff --git a/internal/runner/runner.go b/internal/runner/runner.go
index f7954c2ce7..0986760ce9 100644
--- a/internal/runner/runner.go
+++ b/internal/runner/runner.go
@@ -14,6 +14,8 @@ import (
"time"
"github.com/projectdiscovery/nuclei/v3/internal/pdcp"
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/installer"
uncoverlib "github.com/projectdiscovery/uncover"
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
@@ -33,7 +35,6 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
- "github.com/projectdiscovery/nuclei/v3/pkg/core/inputs/hybrid"
"github.com/projectdiscovery/nuclei/v3/pkg/external/customtemplates"
"github.com/projectdiscovery/nuclei/v3/pkg/input"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
@@ -69,22 +70,23 @@ var (
// Runner is a client for running the enumeration process.
type Runner struct {
- output output.Writer
- interactsh *interactsh.Client
- options *types.Options
- projectFile *projectfile.ProjectFile
- catalog catalog.Catalog
- progress progress.Progress
- colorizer aurora.Aurora
- issuesClient reporting.Client
- hmapInputProvider *hybrid.Input
- browser *engine.Browser
- rateLimiter *ratelimit.Limiter
- hostErrors hosterrorscache.CacheInterface
- resumeCfg *types.ResumeCfg
- pprofServer *http.Server
- // pdcp auto-save options
+ output output.Writer
+ interactsh *interactsh.Client
+ options *types.Options
+ projectFile *projectfile.ProjectFile
+ catalog catalog.Catalog
+ progress progress.Progress
+ colorizer aurora.Aurora
+ issuesClient reporting.Client
+ browser *engine.Browser
+ rateLimiter *ratelimit.Limiter
+ hostErrors hosterrorscache.CacheInterface
+ resumeCfg *types.ResumeCfg
+ pprofServer *http.Server
pdcpUploadErrMsg string
+ inputProvider provider.InputProvider
+ //general purpose temporary directory
+ tmpDir string
}
const pprofServerAddress = "127.0.0.1:8086"
@@ -188,7 +190,7 @@ func New(options *types.Options) (*Runner, error) {
}
if reportingOptions != nil {
- client, err := reporting.New(reportingOptions, options.ReportingDB)
+ client, err := reporting.New(reportingOptions, options.ReportingDB, false)
if err != nil {
return nil, errors.Wrap(err, "could not create issue reporting client")
}
@@ -217,14 +219,12 @@ func New(options *types.Options) (*Runner, error) {
os.Exit(0)
}
- // Initialize the input source
- hmapInput, err := hybrid.New(&hybrid.Options{
- Options: options,
- })
+ // create the input provider and load the inputs
+ inputProvider, err := provider.NewInputProvider(provider.InputOptions{Options: options})
if err != nil {
return nil, errors.Wrap(err, "could not create input provider")
}
- runner.hmapInputProvider = hmapInput
+ runner.inputProvider = inputProvider
// Create the output file if asked
outputWriter, err := output.NewStandardWriter(options)
@@ -315,6 +315,11 @@ func New(options *types.Options) (*Runner, error) {
} else {
runner.rateLimiter = ratelimit.NewUnlimited(context.Background())
}
+
+ if tmpDir, err := os.MkdirTemp("", "nuclei-tmp-*"); err == nil {
+ runner.tmpDir = tmpDir
+ }
+
return runner, nil
}
@@ -337,7 +342,9 @@ func (r *Runner) Close() {
if r.projectFile != nil {
r.projectFile.Close()
}
- r.hmapInputProvider.Close()
+ if r.inputProvider != nil {
+ r.inputProvider.Close()
+ }
protocolinit.Close()
if r.pprofServer != nil {
_ = r.pprofServer.Shutdown(context.Background())
@@ -349,6 +356,9 @@ func (r *Runner) Close() {
if r.browser != nil {
r.browser.Close()
}
+ if r.tmpDir != "" {
+ _ = os.RemoveAll(r.tmpDir)
+ }
}
// setupPDCPUpload sets up the PDCP upload writer
@@ -407,19 +417,38 @@ func (r *Runner) RunEnumeration() error {
// Create the executor options which will be used throughout the execution
// stage by the nuclei engine modules.
executorOpts := protocols.ExecutorOptions{
- Output: r.output,
- Options: r.options,
- Progress: r.progress,
- Catalog: r.catalog,
- IssuesClient: r.issuesClient,
- RateLimiter: r.rateLimiter,
- Interactsh: r.interactsh,
- ProjectFile: r.projectFile,
- Browser: r.browser,
- Colorizer: r.colorizer,
- ResumeCfg: r.resumeCfg,
- ExcludeMatchers: excludematchers.New(r.options.ExcludeMatchers),
- InputHelper: input.NewHelper(),
+ Output: r.output,
+ Options: r.options,
+ Progress: r.progress,
+ Catalog: r.catalog,
+ IssuesClient: r.issuesClient,
+ RateLimiter: r.rateLimiter,
+ Interactsh: r.interactsh,
+ ProjectFile: r.projectFile,
+ Browser: r.browser,
+ Colorizer: r.colorizer,
+ ResumeCfg: r.resumeCfg,
+ ExcludeMatchers: excludematchers.New(r.options.ExcludeMatchers),
+ InputHelper: input.NewHelper(),
+ TemporaryDirectory: r.tmpDir,
+ }
+
+ if len(r.options.SecretsFile) > 0 && !r.options.Validate {
+ authTmplStore, err := GetAuthTmplStore(*r.options, r.catalog, executorOpts)
+ if err != nil {
+ return errors.Wrap(err, "failed to load dynamic auth templates")
+ }
+ authOpts := &authprovider.AuthProviderOptions{SecretsFiles: r.options.SecretsFile}
+ authOpts.LazyFetchSecret = GetLazyAuthFetchCallback(&AuthLazyFetchOptions{
+ TemplateStore: authTmplStore,
+ ExecOpts: executorOpts,
+ })
+ // initialize auth provider
+ provider, err := authprovider.NewAuthProvider(authOpts)
+ if err != nil {
+ return errors.Wrap(err, "could not create auth provider")
+ }
+ executorOpts.AuthProvider = provider
}
if r.options.ShouldUseHostError() {
@@ -438,9 +467,16 @@ func (r *Runner) RunEnumeration() error {
}
executorOpts.WorkflowLoader = workflowLoader
- store, err := loader.New(loader.NewConfig(r.options, r.catalog, executorOpts))
+ // If using input-file flags, only load http fuzzing based templates.
+ loaderConfig := loader.NewConfig(r.options, r.catalog, executorOpts)
+ if !strings.EqualFold(r.options.InputFileMode, "list") || r.options.FuzzTemplates {
+ // if input type is not list (implicitly enable fuzzing)
+ r.options.FuzzTemplates = true
+ loaderConfig.OnlyLoadHTTPFuzzing = true
+ }
+ store, err := loader.New(loaderConfig)
if err != nil {
- return errors.Wrap(err, "could not load templates from config")
+ return errors.Wrap(err, "Could not create loader.")
}
if r.options.Validate {
@@ -459,6 +495,9 @@ func (r *Runner) RunEnumeration() error {
disk.PrintDeprecatedPathsMsgIfApplicable(r.options.Silent)
templates.PrintDeprecatedProtocolNameMsgIfApplicable(r.options.Silent, r.options.Verbose)
+ // purge global caches primarily used for loading templates
+ config.DefaultConfig.PurgeGlobalCache()
+
// add the hosts from the metadata queries of loaded templates into input provider
if r.options.Uncover && len(r.options.UncoverQuery) == 0 {
uncoverOpts := &uncoverlib.Options{
@@ -470,7 +509,7 @@ func (r *Runner) RunEnumeration() error {
}
ret := uncover.GetUncoverTargetsFromMetadata(context.TODO(), store.Templates(), r.options.UncoverField, uncoverOpts)
for host := range ret {
- r.hmapInputProvider.SetWithExclusions(host)
+ _ = r.inputProvider.SetWithExclusions(host)
}
}
// list all templates
@@ -482,6 +521,14 @@ func (r *Runner) RunEnumeration() error {
// display execution info like version , templates used etc
r.displayExecutionInfo(store)
+ // prefetch secrets if enabled
+ if executorOpts.AuthProvider != nil && r.options.PreFetchSecrets {
+ gologger.Info().Msgf("Pre-fetching secrets from authprovider[s]")
+ if err := executorOpts.AuthProvider.PreFetchSecrets(); err != nil {
+ return errors.Wrap(err, "could not pre-fetch secrets")
+ }
+ }
+
// If not explicitly disabled, check if http based protocols
// are used, and if inputs are non-http to pre-perform probing
// of urls and storing them for execution.
@@ -527,7 +574,7 @@ func (r *Runner) RunEnumeration() error {
func (r *Runner) isInputNonHTTP() bool {
var nonURLInput bool
- r.hmapInputProvider.Scan(func(value *contextargs.MetaInput) bool {
+ r.inputProvider.Iterate(func(value *contextargs.MetaInput) bool {
if !strings.Contains(value.Input, "://") {
nonURLInput = true
return false
@@ -538,13 +585,13 @@ func (r *Runner) isInputNonHTTP() bool {
}
func (r *Runner) executeSmartWorkflowInput(executorOpts protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) {
- r.progress.Init(r.hmapInputProvider.Count(), 0, 0)
+ r.progress.Init(r.inputProvider.Count(), 0, 0)
service, err := automaticscan.New(automaticscan.Options{
ExecuterOpts: executorOpts,
Store: store,
Engine: engine,
- Target: r.hmapInputProvider,
+ Target: r.inputProvider,
})
if err != nil {
return nil, errors.Wrap(err, "could not create automatic scan service")
@@ -575,7 +622,12 @@ func (r *Runner) executeTemplatesInput(store *loader.Store, engine *core.Engine)
return nil, errors.New("no templates provided for scan")
}
- results := engine.ExecuteScanWithOpts(finalTemplates, r.hmapInputProvider, r.options.DisableClustering)
+ // pass input provider to engine
+ // TODO: this should be not necessary after r.hmapInputProvider is removed + refactored
+ if r.inputProvider == nil {
+ return nil, errors.New("no input provider found")
+ }
+ results := engine.ExecuteScanWithOpts(finalTemplates, r.inputProvider, r.options.DisableClustering)
return results, nil
}
@@ -589,9 +641,12 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) {
// only print these stats in verbose mode
stats.DisplayAsWarning(parsers.HeadlessFlagWarningStats)
stats.DisplayAsWarning(parsers.CodeFlagWarningStats)
+ stats.DisplayAsWarning(parsers.FuzzFlagWarningStats)
stats.DisplayAsWarning(parsers.TemplatesExecutedStats)
}
- stats.DisplayAsWarning(parsers.UnsignedWarning)
+
+ stats.DisplayAsWarning(parsers.UnsignedCodeWarning)
+ stats.ForceDisplayWarning(parsers.SkippedUnsignedStats)
cfg := config.DefaultConfig
@@ -613,16 +668,23 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) {
gologger.Info().Msgf("Workflows loaded for current scan: %d", len(store.Workflows()))
}
for k, v := range templates.SignatureStats {
- if v.Load() > 0 {
+ value := v.Load()
+ if k == templates.Unsigned && value > 0 {
+ // adjust skipped unsigned templates via code or -dut flag
+ value = value - uint64(stats.GetValue(parsers.SkippedUnsignedStats))
+ value = value - uint64(stats.GetValue(parsers.CodeFlagWarningStats))
+ }
+ if value > 0 {
if k != templates.Unsigned {
- gologger.Info().Msgf("Executing %d signed templates from %s", v.Load(), k)
+ gologger.Info().Msgf("Executing %d signed templates from %s", value, k)
} else if !r.options.Silent && !config.DefaultConfig.HideTemplateSigWarning {
- gologger.Print().Msgf("[%v] Executing %d unsigned templates. Use with caution.", aurora.BrightYellow("WRN"), v.Load())
+ gologger.Print().Msgf("[%v] Loaded %d unsigned templates for scan. Use with caution.", aurora.BrightYellow("WRN"), value)
}
}
}
- if r.hmapInputProvider.Count() > 0 {
- gologger.Info().Msgf("Targets loaded for current scan: %d", r.hmapInputProvider.Count())
+
+ if r.inputProvider.Count() > 0 {
+ gologger.Info().Msgf("Targets loaded for current scan: %d", r.inputProvider.Count())
}
}
diff --git a/lib/config.go b/lib/config.go
index 0c48f373be..58fab00a56 100644
--- a/lib/config.go
+++ b/lib/config.go
@@ -4,9 +4,11 @@ import (
"context"
"time"
+ "github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/ratelimit"
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
@@ -107,10 +109,12 @@ func WithInteractshOptions(opts InteractshOpts) NucleiSDKOptions {
// 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)
+ 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)
+ JavascriptTemplateConcurrency int // number of templates to run concurrently for javascript templates (per host in host-spray mode)
+ TemplatePayloadConcurrency int // max concurrent payloads to run for a template (a good default is 25)
}
// WithConcurrency sets concurrency options
@@ -120,6 +124,8 @@ func WithConcurrency(opts Concurrency) NucleiSDKOptions {
e.opts.BulkSize = opts.HostConcurrency
e.opts.HeadlessBulkSize = opts.HeadlessHostConcurrency
e.opts.HeadlessTemplateThreads = opts.HeadlessTemplateConcurrency
+ e.opts.JsConcurrency = opts.JavascriptTemplateConcurrency
+ e.opts.PayloadConcurrency = opts.TemplatePayloadConcurrency
return nil
}
}
@@ -159,7 +165,7 @@ func EnableHeadlessWithOpts(hopts *HeadlessOpts) NucleiSDKOptions {
if err != nil {
return err
}
- e.executerOpts.Browser = browser
+ e.browserInstance = browser
return nil
}
}
@@ -352,3 +358,28 @@ func EnablePassiveMode() NucleiSDKOptions {
return nil
}
}
+
+// WithAuthOptions allows setting a custom authprovider implementation
+func WithAuthProvider(provider authprovider.AuthProvider) NucleiSDKOptions {
+ return func(e *NucleiEngine) error {
+ e.authprovider = provider
+ return nil
+ }
+}
+
+// LoadSecretsFromFile allows loading secrets from file
+func LoadSecretsFromFile(files []string, prefetch bool) NucleiSDKOptions {
+ return func(e *NucleiEngine) error {
+ e.opts.SecretsFile = goflags.StringSlice(files)
+ e.opts.PreFetchSecrets = prefetch
+ return nil
+ }
+}
+
+// EnableFuzzTemplates allows enabling template fuzzing
+func EnableFuzzTemplates() NucleiSDKOptions {
+ return func(e *NucleiEngine) error {
+ e.opts.FuzzTemplates = true
+ return nil
+ }
+}
diff --git a/lib/multi.go b/lib/multi.go
index daf4e29522..7a3dd7ceb0 100644
--- a/lib/multi.go
+++ b/lib/multi.go
@@ -5,13 +5,13 @@ import (
"time"
"github.com/logrusorgru/aurora"
+ "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
- "github.com/projectdiscovery/nuclei/v3/pkg/core/inputs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/parsers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/ratelimit"
errorutil "github.com/projectdiscovery/utils/errors"
@@ -88,10 +88,11 @@ func (e *ThreadSafeNucleiEngine) GlobalLoadAllTemplates() error {
// 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}
+ config.DefaultConfig.PurgeGlobalCache()
}
// 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
+// This method can be called concurrently and it will use some global resources but can be runned parallelly
// 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 {
@@ -121,14 +122,7 @@ func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOpts(targets []string, opts ..
}
store.Load()
- inputProvider := &inputs.SimpleInputProvider{
- Inputs: []*contextargs.MetaInput{},
- }
-
- // load targets
- for _, target := range targets {
- inputProvider.Set(target)
- }
+ inputProvider := provider.NewSimpleInputProviderWithUrls(targets...)
if len(store.Templates()) == 0 && len(store.Workflows()) == 0 {
return ErrNoTemplatesAvailable
diff --git a/lib/sdk.go b/lib/sdk.go
index 70a2829901..69978409bb 100644
--- a/lib/sdk.go
+++ b/lib/sdk.go
@@ -5,11 +5,12 @@ import (
"bytes"
"io"
- "github.com/projectdiscovery/httpx/common/httpx"
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
- "github.com/projectdiscovery/nuclei/v3/pkg/core/inputs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
+ providerTypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/parsers"
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
@@ -65,12 +66,13 @@ type NucleiEngine struct {
catalog *disk.DiskCatalog
rateLimiter *ratelimit.Limiter
store *loader.Store
- httpxClient *httpx.HTTPX
- inputProvider *inputs.SimpleInputProvider
+ httpxClient providerTypes.InputLivenessProbe
+ inputProvider provider.InputProvider
engine *core.Engine
mode engineMode
browserInstance *engine.Browser
httpClient *retryablehttp.Client
+ authprovider authprovider.AuthProvider
// unexported meta options
opts *types.Options
@@ -110,7 +112,7 @@ func (e *NucleiEngine) GetTemplates() []*templates.Template {
func (e *NucleiEngine) LoadTargets(targets []string, probeNonHttp bool) {
for _, target := range targets {
if probeNonHttp {
- e.inputProvider.SetWithProbe(target, e.httpxClient)
+ _ = e.inputProvider.SetWithProbe(target, e.httpxClient)
} else {
e.inputProvider.Set(target)
}
@@ -122,13 +124,29 @@ 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)
+ _ = e.inputProvider.SetWithProbe(buff.Text(), e.httpxClient)
} else {
e.inputProvider.Set(buff.Text())
}
}
}
+// LoadTargetsWithHttpData loads targets that contain http data from file it currently supports
+// multiple formats like burp xml,openapi,swagger,proxify json
+// Note: this is mutually exclusive with LoadTargets and LoadTargetsFromReader
+func (e *NucleiEngine) LoadTargetsWithHttpData(filePath string, filemode string) error {
+ e.opts.TargetsFilePath = filePath
+ e.opts.InputFileMode = filemode
+ httpProvider, err := provider.NewInputProvider(provider.InputOptions{Options: e.opts})
+ if err != nil {
+ e.opts.TargetsFilePath = ""
+ e.opts.InputFileMode = ""
+ return err
+ }
+ e.inputProvider = httpProvider
+ return nil
+}
+
// GetExecuterOptions returns the nuclei executor options
func (e *NucleiEngine) GetExecuterOptions() *protocols.ExecutorOptions {
return &e.executerOpts
diff --git a/lib/sdk_private.go b/lib/sdk_private.go
index 3281ebdbef..2e3f1cb147 100644
--- a/lib/sdk_private.go
+++ b/lib/sdk_private.go
@@ -8,19 +8,20 @@ import (
"time"
"github.com/logrusorgru/aurora"
+ "github.com/pkg/errors"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/httpx/common/httpx"
"github.com/projectdiscovery/nuclei/v3/internal/runner"
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
- "github.com/projectdiscovery/nuclei/v3/pkg/core/inputs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/installer"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
@@ -29,36 +30,41 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
+ nucleiUtils "github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/ratelimit"
)
// applyRequiredDefaults to options
func (e *NucleiEngine) applyRequiredDefaults() {
- if e.customWriter == nil {
- mockoutput := testutils.NewMockOutputWriter(e.opts.OmitTemplate)
- mockoutput.WriteCallback = func(event *output.ResultEvent) {
- if len(e.resultCallbacks) > 0 {
- for _, callback := range e.resultCallbacks {
- if callback != nil {
- callback(event)
- }
+ mockoutput := testutils.NewMockOutputWriter(e.opts.OmitTemplate)
+ 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())
+ return
}
- if e.onFailureCallback != nil {
- mockoutput.FailureCallback = e.onFailureCallback
+ 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
+ }
+
+ if e.customWriter != nil {
+ e.customWriter = output.NewMultiWriter(e.customWriter, mockoutput)
+ } else {
e.customWriter = mockoutput
}
+
if e.customProgress == nil {
e.customProgress = &testutils.MockProgressClient{}
}
@@ -82,9 +88,7 @@ func (e *NucleiEngine) applyRequiredDefaults() {
// and idea is to disable them to avoid false positives
e.opts.ExcludeTags = append(e.opts.ExcludeTags, config.ReadIgnoreFile().Tags...)
- e.inputProvider = &inputs.SimpleInputProvider{
- Inputs: []*contextargs.MetaInput{},
- }
+ e.inputProvider = provider.NewSimpleInputProvider()
}
// init
@@ -128,7 +132,7 @@ func (e *NucleiEngine) init() error {
return err
}
// we don't support reporting config in sdk mode
- if e.rc, err = reporting.New(&reporting.Options{}, ""); err != nil {
+ if e.rc, err = reporting.New(&reporting.Options{}, "", false); err != nil {
return err
}
e.interactshOpts.IssuesClient = e.rc
@@ -154,6 +158,33 @@ func (e *NucleiEngine) init() error {
ResumeCfg: types.NewResumeCfg(),
Browser: e.browserInstance,
}
+ if len(e.opts.SecretsFile) > 0 {
+ authTmplStore, err := runner.GetAuthTmplStore(*e.opts, e.catalog, e.executerOpts)
+ if err != nil {
+ return errors.Wrap(err, "failed to load dynamic auth templates")
+ }
+ authOpts := &authprovider.AuthProviderOptions{SecretsFiles: e.opts.SecretsFile}
+ authOpts.LazyFetchSecret = runner.GetLazyAuthFetchCallback(&runner.AuthLazyFetchOptions{
+ TemplateStore: authTmplStore,
+ ExecOpts: e.executerOpts,
+ })
+ // initialize auth provider
+ provider, err := authprovider.NewAuthProvider(authOpts)
+ if err != nil {
+ return errors.Wrap(err, "could not create auth provider")
+ }
+ e.executerOpts.AuthProvider = provider
+ }
+ if e.authprovider != nil {
+ e.executerOpts.AuthProvider = e.authprovider
+ }
+
+ // prefetch secrets
+ if e.executerOpts.AuthProvider != nil && e.opts.PreFetchSecrets {
+ if err := e.executerOpts.AuthProvider.PreFetchSecrets(); err != nil {
+ return errors.Wrap(err, "could not prefetch secrets")
+ }
+ }
if e.opts.RateLimitMinute > 0 {
e.executerOpts.RateLimiter = ratelimit.New(context.Background(), uint(e.opts.RateLimitMinute), time.Minute)
@@ -168,8 +199,10 @@ func (e *NucleiEngine) init() error {
httpxOptions := httpx.DefaultOptions
httpxOptions.Timeout = 5 * time.Second
- if e.httpxClient, err = httpx.New(&httpxOptions); err != nil {
+ if client, err := httpx.New(&httpxOptions); err != nil {
return err
+ } else {
+ e.httpxClient = nucleiUtils.GetInputLivenessChecker(client)
}
// Only Happens once regardless how many times this function is called
diff --git a/memogen b/memogen
new file mode 100755
index 0000000000..baab3a72be
Binary files /dev/null and b/memogen differ
diff --git a/nuclei-jsonschema.json b/nuclei-jsonschema.json
index e9ab2c3033..b161393bcb 100644
--- a/nuclei-jsonschema.json
+++ b/nuclei-jsonschema.json
@@ -2,6 +2,101 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/templates.Template",
"definitions": {
+ "fuzz.Rule": {
+ "properties": {
+ "type": {
+ "enum": [
+ "replace",
+ "prefix",
+ "postfix",
+ "infix",
+ "replace-regex"
+ ],
+ "type": "string",
+ "title": "type of rule",
+ "description": "Type of fuzzing rule to perform"
+ },
+ "part": {
+ "enum": [
+ "query",
+ "header",
+ "path",
+ "body",
+ "cookie",
+ "request"
+ ],
+ "type": "string",
+ "title": "part of rule",
+ "description": "Part of request rule to fuzz"
+ },
+ "mode": {
+ "enum": [
+ "single",
+ "multiple"
+ ],
+ "type": "string",
+ "title": "mode of rule",
+ "description": "Mode of request rule to fuzz"
+ },
+ "keys": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "keys of parameters to fuzz",
+ "description": "Keys of parameters to fuzz"
+ },
+ "keys-regex": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "keys regex to fuzz",
+ "description": "Regex of parameter keys to fuzz"
+ },
+ "values": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "title": "values regex to fuzz",
+ "description": "Regex of parameter values to fuzz"
+ },
+ "fuzz": {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "$ref": "#/definitions/fuzz.SliceOrMapSlice",
+ "title": "payloads of fuzz rule",
+ "description": "Payloads to perform fuzzing substitutions with"
+ },
+ "replace-regex": {
+ "type": "string",
+ "title": "replace regex of rule",
+ "description": "Regex for regex-replace rule type"
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ },
+ "fuzz.SliceOrMapSlice": {
+ "required": [
+ "Value",
+ "KV"
+ ],
+ "properties": {
+ "Value": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "KV": {
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "$ref": "#/definitions/github.com/projectdiscovery/utils/maps.OrderedMap[string,string]"
+ }
+ },
+ "additionalProperties": false,
+ "type": "object"
+ },
"model.Classification": {
"properties": {
"cve-id": {
@@ -245,11 +340,6 @@
"type": "boolean",
"title": "use case insensitive extract",
"description": "use case insensitive extract"
- },
- "to": {
- "type": "string",
- "title": "save extracted values to file",
- "description": "save extracted values to file"
}
},
"additionalProperties": false,
@@ -375,6 +465,11 @@
"type": "boolean",
"title": "match all values",
"description": "match all matcher values ignoring condition"
+ },
+ "internal": {
+ "type": "boolean",
+ "title": "hide matcher from output",
+ "description": "hide matcher from output"
}
},
"additionalProperties": false,
@@ -428,11 +523,6 @@
},
"engine": {
"items": {
- "enum": [
- "python",
- "powershell",
- "command"
- ],
"type": "string"
},
"type": "array",
@@ -461,72 +551,6 @@
"additionalProperties": false,
"type": "object"
},
- "fuzz.Rule": {
- "properties": {
- "type": {
- "enum": [
- "replace",
- "prefix",
- "postfix",
- "infix"
- ],
- "type": "string",
- "title": "type of rule",
- "description": "Type of fuzzing rule to perform"
- },
- "part": {
- "enum": [
- "query"
- ],
- "type": "string",
- "title": "part of rule",
- "description": "Part of request rule to fuzz"
- },
- "mode": {
- "enum": [
- "single",
- "multiple"
- ],
- "type": "string",
- "title": "mode of rule",
- "description": "Mode of request rule to fuzz"
- },
- "keys": {
- "items": {
- "type": "string"
- },
- "type": "array",
- "title": "keys of parameters to fuzz",
- "description": "Keys of parameters to fuzz"
- },
- "keys-regex": {
- "items": {
- "type": "string"
- },
- "type": "array",
- "title": "keys regex to fuzz",
- "description": "Regex of parameter keys to fuzz"
- },
- "values": {
- "items": {
- "type": "string"
- },
- "type": "array",
- "title": "values regex to fuzz",
- "description": "Regex of parameter values to fuzz"
- },
- "fuzz": {
- "items": {
- "type": "string"
- },
- "type": "array",
- "title": "payloads of fuzz rule",
- "description": "Payloads to perform fuzzing substitutions with"
- }
- },
- "additionalProperties": false,
- "type": "object"
- },
"generators.AttackTypeHolder": {
"enum": [
"batteringram",
@@ -648,6 +672,11 @@
"title": "payloads for the network request",
"description": "Payloads contains any payloads for the current request"
},
+ "threads": {
+ "type": "integer",
+ "title": "threads for sending requests",
+ "description": "Threads specifies number of threads to use sending requests. This enables Connection Pooling"
+ },
"recursion": {
"type": "boolean",
"title": "recurse all servers",
@@ -821,6 +850,11 @@
"type": "boolean",
"title": "optional cookie reuse enable",
"description": "Optional setting that enables cookie reuse"
+ },
+ "disable-cookie": {
+ "type": "boolean",
+ "title": "optional disable cookie reuse",
+ "description": "Optional setting that disables cookie reuse"
}
},
"additionalProperties": false,
@@ -1038,6 +1072,9 @@
"title": "fuzzin rules for http fuzzing",
"description": "Fuzzing describes rule schema to fuzz http requests"
},
+ "self-contained": {
+ "type": "boolean"
+ },
"signature": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/http.SignatureTypeHolder",
@@ -1049,6 +1086,11 @@
"title": "optional cookie reuse enable",
"description": "Optional setting that enables cookie reuse"
},
+ "disable-cookie": {
+ "type": "boolean",
+ "title": "optional disable cookie reuse",
+ "description": "Optional setting that disables cookie reuse"
+ },
"read-all": {
"type": "boolean",
"title": "force read all body",
@@ -1113,6 +1155,23 @@
"type": "boolean",
"title": "disable auto merging of path",
"description": "Disable merging target url path with raw request path"
+ },
+ "filters": {
+ "items": {
+ "$ref": "#/definitions/matchers.Matcher"
+ },
+ "type": "array",
+ "title": "filter for fuzzing",
+ "description": "Filter is matcher-like field to check if fuzzing should be performed on this request or not"
+ },
+ "filters-condition": {
+ "enum": [
+ "and",
+ "or"
+ ],
+ "type": "string",
+ "title": "condition between the filters",
+ "description": "Conditions between the filters"
}
},
"additionalProperties": false,
@@ -1181,6 +1240,11 @@
"title": "code to execute in javascript",
"description": "Executes inline javascript code for the request"
},
+ "timeout": {
+ "type": "integer",
+ "title": "timeout for javascript execution",
+ "description": "Timeout in seconds is optional timeout for entire javascript script execution"
+ },
"stop-at-first-match": {
"type": "boolean",
"title": "stop at first match",
@@ -1276,6 +1340,11 @@
"title": "payloads for the network request",
"description": "Payloads contains any payloads for the current request"
},
+ "threads": {
+ "type": "integer",
+ "title": "threads for sending requests",
+ "description": "Threads specifies number of threads to use sending requests. This enables Connection Pooling"
+ },
"inputs": {
"items": {
"$schema": "http://json-schema.org/draft-04/schema#",
@@ -1410,6 +1479,30 @@
"type": "string",
"title": "Scan Mode",
"description": "Scan Mode - auto if not specified."
+ },
+ "tls_version_enum": {
+ "type": "boolean",
+ "title": "Enumerate Versions",
+ "description": "Enumerate Version - false if not specified"
+ },
+ "tls_cipher_enum": {
+ "type": "boolean",
+ "title": "Enumerate Ciphers",
+ "description": "Enumerate Ciphers - false if not specified"
+ },
+ "tls_cipher_types": {
+ "items": {
+ "enum": [
+ "weak",
+ "secure",
+ "insecure",
+ "all"
+ ],
+ "type": "string"
+ },
+ "type": "array",
+ "title": "TLS Cipher Types",
+ "description": "TLS Cipher Types to enumerate"
}
},
"additionalProperties": false,
@@ -1788,6 +1881,11 @@
},
"additionalProperties": false,
"type": "object"
+ },
+ "github.com/projectdiscovery/utils/maps.OrderedMap[string,string]": {
+ "properties": {},
+ "additionalProperties": false,
+ "type": "object"
}
}
}
diff --git a/pkg/authprovider/authx/basic_auth.go b/pkg/authprovider/authx/basic_auth.go
new file mode 100644
index 0000000000..b75790662d
--- /dev/null
+++ b/pkg/authprovider/authx/basic_auth.go
@@ -0,0 +1,31 @@
+package authx
+
+import (
+ "net/http"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+var (
+ _ AuthStrategy = &BasicAuthStrategy{}
+)
+
+// BasicAuthStrategy is a strategy for basic auth
+type BasicAuthStrategy struct {
+ Data *Secret
+}
+
+// NewBasicAuthStrategy creates a new basic auth strategy
+func NewBasicAuthStrategy(data *Secret) *BasicAuthStrategy {
+ return &BasicAuthStrategy{Data: data}
+}
+
+// Apply applies the basic auth strategy to the request
+func (s *BasicAuthStrategy) Apply(req *http.Request) {
+ req.SetBasicAuth(s.Data.Username, s.Data.Password)
+}
+
+// ApplyOnRR applies the basic auth strategy to the retryable request
+func (s *BasicAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {
+ req.SetBasicAuth(s.Data.Username, s.Data.Password)
+}
diff --git a/pkg/authprovider/authx/bearer_auth.go b/pkg/authprovider/authx/bearer_auth.go
new file mode 100644
index 0000000000..edf6f439b3
--- /dev/null
+++ b/pkg/authprovider/authx/bearer_auth.go
@@ -0,0 +1,31 @@
+package authx
+
+import (
+ "net/http"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+var (
+ _ AuthStrategy = &BearerTokenAuthStrategy{}
+)
+
+// BearerTokenAuthStrategy is a strategy for bearer token auth
+type BearerTokenAuthStrategy struct {
+ Data *Secret
+}
+
+// NewBearerTokenAuthStrategy creates a new bearer token auth strategy
+func NewBearerTokenAuthStrategy(data *Secret) *BearerTokenAuthStrategy {
+ return &BearerTokenAuthStrategy{Data: data}
+}
+
+// Apply applies the bearer token auth strategy to the request
+func (s *BearerTokenAuthStrategy) Apply(req *http.Request) {
+ req.Header.Set("Authorization", "Bearer "+s.Data.Token)
+}
+
+// ApplyOnRR applies the bearer token auth strategy to the retryable request
+func (s *BearerTokenAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {
+ req.Header.Set("Authorization", "Bearer "+s.Data.Token)
+}
diff --git a/pkg/authprovider/authx/cookies_auth.go b/pkg/authprovider/authx/cookies_auth.go
new file mode 100644
index 0000000000..7f3e756a71
--- /dev/null
+++ b/pkg/authprovider/authx/cookies_auth.go
@@ -0,0 +1,43 @@
+package authx
+
+import (
+ "net/http"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+var (
+ _ AuthStrategy = &CookiesAuthStrategy{}
+)
+
+// CookiesAuthStrategy is a strategy for cookies auth
+type CookiesAuthStrategy struct {
+ Data *Secret
+}
+
+// NewCookiesAuthStrategy creates a new cookies auth strategy
+func NewCookiesAuthStrategy(data *Secret) *CookiesAuthStrategy {
+ return &CookiesAuthStrategy{Data: data}
+}
+
+// Apply applies the cookies auth strategy to the request
+func (s *CookiesAuthStrategy) Apply(req *http.Request) {
+ for _, cookie := range s.Data.Cookies {
+ c := &http.Cookie{
+ Name: cookie.Key,
+ Value: cookie.Value,
+ }
+ req.AddCookie(c)
+ }
+}
+
+// ApplyOnRR applies the cookies auth strategy to the retryable request
+func (s *CookiesAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {
+ for _, cookie := range s.Data.Cookies {
+ c := &http.Cookie{
+ Name: cookie.Key,
+ Value: cookie.Value,
+ }
+ req.AddCookie(c)
+ }
+}
diff --git a/pkg/authprovider/authx/dynamic.go b/pkg/authprovider/authx/dynamic.go
new file mode 100644
index 0000000000..0e210cf5e7
--- /dev/null
+++ b/pkg/authprovider/authx/dynamic.go
@@ -0,0 +1,167 @@
+package authx
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+ "sync"
+
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer"
+ errorutil "github.com/projectdiscovery/utils/errors"
+)
+
+type LazyFetchSecret func(d *Dynamic) error
+
+var (
+ _ json.Unmarshaler = &Dynamic{}
+)
+
+// Dynamic is a struct for dynamic secret or credential
+// these are high level secrets that take action to generate the actual secret
+// ex: username and password are dynamic secrets, the actual secret is the token obtained
+// after authenticating with the username and password
+type Dynamic struct {
+ Secret `yaml:",inline"` // this is a static secret that will be generated after the dynamic secret is resolved
+ TemplatePath string `json:"template" yaml:"template"`
+ Variables []KV `json:"variables" yaml:"variables"`
+ Input string `json:"input" yaml:"input"` // (optional) target for the dynamic secret
+ Extracted map[string]interface{} `json:"-" yaml:"-"` // extracted values from the dynamic secret
+ fetchCallback LazyFetchSecret `json:"-" yaml:"-"`
+ m *sync.Mutex `json:"-" yaml:"-"` // mutex for lazy fetch
+ fetched bool `json:"-" yaml:"-"` // flag to check if the secret has been fetched
+ error error `json:"-" yaml:"-"` // error if any
+}
+
+func (d *Dynamic) UnmarshalJSON(data []byte) error {
+ if err := json.Unmarshal(data, &d); err != nil {
+ return err
+ }
+ var s Secret
+ if err := json.Unmarshal(data, &s); err != nil {
+ return err
+ }
+ d.Secret = s
+ return nil
+}
+
+// Validate validates the dynamic secret
+func (d *Dynamic) Validate() error {
+ d.m = &sync.Mutex{}
+ if d.TemplatePath == "" {
+ return errorutil.New(" template-path is required for dynamic secret")
+ }
+ if len(d.Variables) == 0 {
+ return errorutil.New("variables are required for dynamic secret")
+ }
+ d.skipCookieParse = true // skip cookie parsing in dynamic secrets during validation
+ if err := d.Secret.Validate(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// SetLazyFetchCallback sets the lazy fetch callback for the dynamic secret
+func (d *Dynamic) SetLazyFetchCallback(callback LazyFetchSecret) {
+ d.fetchCallback = func(d *Dynamic) error {
+ err := callback(d)
+ d.fetched = true
+ if err != nil {
+ d.error = err
+ return err
+ }
+ if len(d.Extracted) == 0 {
+ return fmt.Errorf("no extracted values found for dynamic secret")
+ }
+
+ // evaluate headers
+ for i, header := range d.Headers {
+ if strings.Contains(header.Value, "{{") {
+ header.Value = replacer.Replace(header.Value, d.Extracted)
+ }
+ if strings.Contains(header.Key, "{{") {
+ header.Key = replacer.Replace(header.Key, d.Extracted)
+ }
+ d.Headers[i] = header
+ }
+
+ // evaluate cookies
+ for i, cookie := range d.Cookies {
+ if strings.Contains(cookie.Value, "{{") {
+ cookie.Value = replacer.Replace(cookie.Value, d.Extracted)
+ }
+ if strings.Contains(cookie.Key, "{{") {
+ cookie.Key = replacer.Replace(cookie.Key, d.Extracted)
+ }
+ if strings.Contains(cookie.Raw, "{{") {
+ cookie.Raw = replacer.Replace(cookie.Raw, d.Extracted)
+ }
+ d.Cookies[i] = cookie
+ }
+
+ // evaluate query params
+ for i, query := range d.Params {
+ if strings.Contains(query.Value, "{{") {
+ query.Value = replacer.Replace(query.Value, d.Extracted)
+ }
+ if strings.Contains(query.Key, "{{") {
+ query.Key = replacer.Replace(query.Key, d.Extracted)
+ }
+ d.Params[i] = query
+ }
+
+ // check username, password and token
+ if strings.Contains(d.Username, "{{") {
+ d.Username = replacer.Replace(d.Username, d.Extracted)
+ }
+ if strings.Contains(d.Password, "{{") {
+ d.Password = replacer.Replace(d.Password, d.Extracted)
+ }
+ if strings.Contains(d.Token, "{{") {
+ d.Token = replacer.Replace(d.Token, d.Extracted)
+ }
+
+ // now attempt to parse the cookies
+ d.skipCookieParse = false
+ for i, cookie := range d.Cookies {
+ if cookie.Raw != "" {
+ if err := cookie.Parse(); err != nil {
+ return fmt.Errorf("[%s] invalid raw cookie in cookiesAuth: %s", d.TemplatePath, err)
+ }
+ d.Cookies[i] = cookie
+ }
+ }
+ return nil
+ }
+}
+
+// GetStrategy returns the auth strategy for the dynamic secret
+func (d *Dynamic) GetStrategy() AuthStrategy {
+ if !d.fetched {
+ _ = d.Fetch(true)
+ }
+ if d.error != nil {
+ return nil
+ }
+ return d.Secret.GetStrategy()
+}
+
+// Fetch fetches the dynamic secret
+// if isFatal is true, it will stop the execution if the secret could not be fetched
+func (d *Dynamic) Fetch(isFatal bool) error {
+ d.m.Lock()
+ defer d.m.Unlock()
+ if d.fetched {
+ return nil
+ }
+ d.error = d.fetchCallback(d)
+ if d.error != nil && isFatal {
+ gologger.Fatal().Msgf("Could not fetch dynamic secret: %s\n", d.error)
+ }
+ return d.error
+}
+
+// Error returns the error if any
+func (d *Dynamic) Error() error {
+ return d.error
+}
diff --git a/pkg/authprovider/authx/file.go b/pkg/authprovider/authx/file.go
new file mode 100644
index 0000000000..698aafe804
--- /dev/null
+++ b/pkg/authprovider/authx/file.go
@@ -0,0 +1,253 @@
+package authx
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ errorutil "github.com/projectdiscovery/utils/errors"
+ "github.com/projectdiscovery/utils/generic"
+ stringsutil "github.com/projectdiscovery/utils/strings"
+ "gopkg.in/yaml.v3"
+)
+
+type AuthType string
+
+const (
+ BasicAuth AuthType = "BasicAuth"
+ BearerTokenAuth AuthType = "BearerToken"
+ HeadersAuth AuthType = "Header"
+ CookiesAuth AuthType = "Cookie"
+ QueryAuth AuthType = "Query"
+)
+
+// SupportedAuthTypes returns the supported auth types
+func SupportedAuthTypes() []string {
+ return []string{
+ string(BasicAuth),
+ string(BearerTokenAuth),
+ string(HeadersAuth),
+ string(CookiesAuth),
+ string(QueryAuth),
+ }
+}
+
+// Authx is a struct for secrets or credentials file
+type Authx struct {
+ ID string `json:"id" yaml:"id"`
+ Info AuthFileInfo `json:"info" yaml:"info"`
+ Secrets []Secret `json:"static" yaml:"static"`
+ Dynamic []Dynamic `json:"dynamic" yaml:"dynamic"`
+}
+
+type AuthFileInfo struct {
+ Name string `json:"name" yaml:"name"`
+ Author string `json:"author" yaml:"author"`
+ Severity string `json:"severity" yaml:"severity"`
+ Description string `json:"description" yaml:"description"`
+}
+
+// Secret is a struct for secret or credential
+type Secret struct {
+ Type string `json:"type" yaml:"type"`
+ Domains []string `json:"domains" yaml:"domains"`
+ DomainsRegex []string `json:"domains-regex" yaml:"domains-regex"`
+ Headers []KV `json:"headers" yaml:"headers"`
+ Cookies []Cookie `json:"cookies" yaml:"cookies"`
+ Params []KV `json:"params" yaml:"params"`
+ Username string `json:"username" yaml:"username"` // can be either email or username
+ Password string `json:"password" yaml:"password"`
+ Token string `json:"token" yaml:"token"` // Bearer Auth token
+ skipCookieParse bool `json:"-" yaml:"-"` // temporary flag to skip cookie parsing (used in dynamic secrets)
+}
+
+// GetStrategy returns the auth strategy for the secret
+func (s *Secret) GetStrategy() AuthStrategy {
+ switch {
+ case strings.EqualFold(s.Type, string(BasicAuth)):
+ return NewBasicAuthStrategy(s)
+ case strings.EqualFold(s.Type, string(BearerTokenAuth)):
+ return NewBearerTokenAuthStrategy(s)
+ case strings.EqualFold(s.Type, string(HeadersAuth)):
+ return NewHeadersAuthStrategy(s)
+ case strings.EqualFold(s.Type, string(CookiesAuth)):
+ return NewCookiesAuthStrategy(s)
+ case strings.EqualFold(s.Type, string(QueryAuth)):
+ return NewQueryAuthStrategy(s)
+ }
+ return nil
+}
+
+func (s *Secret) Validate() error {
+ if !stringsutil.EqualFoldAny(s.Type, SupportedAuthTypes()...) {
+ return fmt.Errorf("invalid type: %s", s.Type)
+ }
+ if len(s.Domains) == 0 && len(s.DomainsRegex) == 0 {
+ return fmt.Errorf("domains or domains-regex cannot be empty")
+ }
+ if len(s.DomainsRegex) > 0 {
+ for _, domain := range s.DomainsRegex {
+ _, err := regexp.Compile(domain)
+ if err != nil {
+ return fmt.Errorf("invalid domain regex: %s", domain)
+ }
+ }
+ }
+
+ switch {
+ case strings.EqualFold(s.Type, string(BasicAuth)):
+ if s.Username == "" {
+ return fmt.Errorf("username cannot be empty in basic auth")
+ }
+ if s.Password == "" {
+ return fmt.Errorf("password cannot be empty in basic auth")
+ }
+ case strings.EqualFold(s.Type, string(BearerTokenAuth)):
+ if s.Token == "" {
+ return fmt.Errorf("token cannot be empty in bearer token auth")
+ }
+ case strings.EqualFold(s.Type, string(HeadersAuth)):
+ if len(s.Headers) == 0 {
+ return fmt.Errorf("headers cannot be empty in headers auth")
+ }
+ for _, header := range s.Headers {
+ if err := header.Validate(); err != nil {
+ return fmt.Errorf("invalid header in headersAuth: %s", err)
+ }
+ }
+ case strings.EqualFold(s.Type, string(CookiesAuth)):
+ if len(s.Cookies) == 0 {
+ return fmt.Errorf("cookies cannot be empty in cookies auth")
+ }
+ for _, cookie := range s.Cookies {
+ if cookie.Raw != "" && !s.skipCookieParse {
+ if err := cookie.Parse(); err != nil {
+ return fmt.Errorf("invalid raw cookie in cookiesAuth: %s", err)
+ }
+ }
+ if err := cookie.Validate(); err != nil {
+ return fmt.Errorf("invalid cookie in cookiesAuth: %s", err)
+ }
+ }
+ case strings.EqualFold(s.Type, string(QueryAuth)):
+ if len(s.Params) == 0 {
+ return fmt.Errorf("query cannot be empty in query auth")
+ }
+ for _, query := range s.Params {
+ if err := query.Validate(); err != nil {
+ return fmt.Errorf("invalid query in queryAuth: %s", err)
+ }
+ }
+ default:
+ return fmt.Errorf("invalid type: %s", s.Type)
+ }
+ return nil
+}
+
+type KV struct {
+ Key string `json:"key" yaml:"key"`
+ Value string `json:"value" yaml:"value"`
+}
+
+func (k *KV) Validate() error {
+ if k.Key == "" {
+ return fmt.Errorf("key cannot be empty")
+ }
+ if k.Value == "" {
+ return fmt.Errorf("value cannot be empty")
+ }
+ return nil
+}
+
+type Cookie struct {
+ Key string `json:"key" yaml:"key"`
+ Value string `json:"value" yaml:"value"`
+ Raw string `json:"raw" yaml:"raw"`
+}
+
+func (c *Cookie) Validate() error {
+ if c.Raw != "" {
+ return nil
+ }
+ if c.Key == "" {
+ return fmt.Errorf("key cannot be empty")
+ }
+ if c.Value == "" {
+ return fmt.Errorf("value cannot be empty")
+ }
+ return nil
+}
+
+// Parse parses the cookie
+// in raw the cookie is in format of
+// Set-Cookie: =; Expires=; Path=; Domain=; Secure; HttpOnly
+func (c *Cookie) Parse() error {
+ if c.Raw == "" {
+ return fmt.Errorf("raw cookie cannot be empty")
+ }
+ tmp := strings.TrimPrefix(c.Raw, "Set-Cookie: ")
+ slice := strings.Split(tmp, ";")
+ if len(slice) == 0 {
+ return fmt.Errorf("invalid raw cookie no ; found")
+ }
+ // first element is the cookie name and value
+ cookie := strings.Split(slice[0], "=")
+ if len(cookie) == 2 {
+ c.Key = cookie[0]
+ c.Value = cookie[1]
+ return nil
+ }
+ return fmt.Errorf("invalid raw cookie: %s", c.Raw)
+}
+
+// GetAuthDataFromFile reads the auth data from file
+func GetAuthDataFromFile(file string) (*Authx, error) {
+ ext := filepath.Ext(file)
+ if !generic.EqualsAny(ext, ".yml", ".yaml", ".json") {
+ return nil, fmt.Errorf("invalid file extension: supported extensions are .yml,.yaml and .json got %s", ext)
+ }
+ bin, err := os.ReadFile(file)
+ if err != nil {
+ return nil, err
+ }
+ if ext == ".yml" || ext == ".yaml" {
+ return GetAuthDataFromYAML(bin)
+ }
+ return GetAuthDataFromJSON(bin)
+}
+
+// GetTemplateIDsFromSecretFile reads the template IDs from the secret file
+func GetTemplatePathsFromSecretFile(file string) ([]string, error) {
+ auth, err := GetAuthDataFromFile(file)
+ if err != nil {
+ return nil, err
+ }
+ var paths []string
+ for _, dynamic := range auth.Dynamic {
+ paths = append(paths, dynamic.TemplatePath)
+ }
+ return paths, nil
+}
+
+// GetAuthDataFromYAML reads the auth data from yaml
+func GetAuthDataFromYAML(data []byte) (*Authx, error) {
+ var auth Authx
+ err := yaml.Unmarshal(data, &auth)
+ if err != nil {
+ return nil, errorutil.NewWithErr(err).Msgf("could not unmarshal yaml")
+ }
+ return &auth, nil
+}
+
+// GetAuthDataFromJSON reads the auth data from json
+func GetAuthDataFromJSON(data []byte) (*Authx, error) {
+ var auth Authx
+ err := json.Unmarshal(data, &auth)
+ if err != nil {
+ return nil, errorutil.NewWithErr(err).Msgf("could not unmarshal json")
+ }
+ return &auth, nil
+}
diff --git a/pkg/authprovider/authx/file_test.go b/pkg/authprovider/authx/file_test.go
new file mode 100644
index 0000000000..0e7ada81c1
--- /dev/null
+++ b/pkg/authprovider/authx/file_test.go
@@ -0,0 +1,20 @@
+package authx
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestSecretsUnmarshal(t *testing.T) {
+ loc := "testData/example-auth.yaml"
+ data, err := GetAuthDataFromFile(loc)
+ require.Nil(t, err, "could not read secrets file")
+ require.NotNil(t, data, "could not read secrets file")
+ for _, s := range data.Secrets {
+ require.Nil(t, s.Validate(), "could not validate secret")
+ }
+ for _, d := range data.Dynamic {
+ require.Nil(t, d.Validate(), "could not validate dynamic")
+ }
+}
diff --git a/pkg/authprovider/authx/headers_auth.go b/pkg/authprovider/authx/headers_auth.go
new file mode 100644
index 0000000000..b3ede114d4
--- /dev/null
+++ b/pkg/authprovider/authx/headers_auth.go
@@ -0,0 +1,35 @@
+package authx
+
+import (
+ "net/http"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+var (
+ _ AuthStrategy = &HeadersAuthStrategy{}
+)
+
+// HeadersAuthStrategy is a strategy for headers auth
+type HeadersAuthStrategy struct {
+ Data *Secret
+}
+
+// NewHeadersAuthStrategy creates a new headers auth strategy
+func NewHeadersAuthStrategy(data *Secret) *HeadersAuthStrategy {
+ return &HeadersAuthStrategy{Data: data}
+}
+
+// Apply applies the headers auth strategy to the request
+func (s *HeadersAuthStrategy) Apply(req *http.Request) {
+ for _, header := range s.Data.Headers {
+ req.Header.Set(header.Key, header.Value)
+ }
+}
+
+// ApplyOnRR applies the headers auth strategy to the retryable request
+func (s *HeadersAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {
+ for _, header := range s.Data.Headers {
+ req.Header.Set(header.Key, header.Value)
+ }
+}
diff --git a/pkg/authprovider/authx/query_auth.go b/pkg/authprovider/authx/query_auth.go
new file mode 100644
index 0000000000..796d8b1feb
--- /dev/null
+++ b/pkg/authprovider/authx/query_auth.go
@@ -0,0 +1,42 @@
+package authx
+
+import (
+ "net/http"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+ urlutil "github.com/projectdiscovery/utils/url"
+)
+
+var (
+ _ AuthStrategy = &QueryAuthStrategy{}
+)
+
+// QueryAuthStrategy is a strategy for query auth
+type QueryAuthStrategy struct {
+ Data *Secret
+}
+
+// NewQueryAuthStrategy creates a new query auth strategy
+func NewQueryAuthStrategy(data *Secret) *QueryAuthStrategy {
+ return &QueryAuthStrategy{Data: data}
+}
+
+// Apply applies the query auth strategy to the request
+func (s *QueryAuthStrategy) Apply(req *http.Request) {
+ q := urlutil.NewOrderedParams()
+ q.Decode(req.URL.RawQuery)
+ for _, p := range s.Data.Params {
+ q.Add(p.Key, p.Value)
+ }
+ req.URL.RawQuery = q.Encode()
+}
+
+// ApplyOnRR applies the query auth strategy to the retryable request
+func (s *QueryAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {
+ q := urlutil.NewOrderedParams()
+ q.Decode(req.Request.URL.RawQuery)
+ for _, p := range s.Data.Params {
+ q.Add(p.Key, p.Value)
+ }
+ req.Request.URL.RawQuery = q.Encode()
+}
diff --git a/pkg/authprovider/authx/strategy.go b/pkg/authprovider/authx/strategy.go
new file mode 100644
index 0000000000..8204083989
--- /dev/null
+++ b/pkg/authprovider/authx/strategy.go
@@ -0,0 +1,39 @@
+package authx
+
+import (
+ "net/http"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+// AuthStrategy is an interface for auth strategies
+// basic auth , bearer token, headers, cookies, query
+type AuthStrategy interface {
+ // Apply applies the strategy to the request
+ Apply(*http.Request)
+ // ApplyOnRR applies the strategy to the retryable request
+ ApplyOnRR(*retryablehttp.Request)
+}
+
+// DynamicAuthStrategy is an auth strategy for dynamic secrets
+// it implements the AuthStrategy interface
+type DynamicAuthStrategy struct {
+ // Dynamic is the dynamic secret to use
+ Dynamic Dynamic
+}
+
+// Apply applies the strategy to the request
+func (d *DynamicAuthStrategy) Apply(req *http.Request) {
+ strategy := d.Dynamic.GetStrategy()
+ if strategy != nil {
+ strategy.Apply(req)
+ }
+}
+
+// ApplyOnRR applies the strategy to the retryable request
+func (d *DynamicAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {
+ strategy := d.Dynamic.GetStrategy()
+ if strategy != nil {
+ strategy.ApplyOnRR(req)
+ }
+}
diff --git a/pkg/authprovider/authx/testData/example-auth.yaml b/pkg/authprovider/authx/testData/example-auth.yaml
new file mode 100644
index 0000000000..0c3175090d
--- /dev/null
+++ b/pkg/authprovider/authx/testData/example-auth.yaml
@@ -0,0 +1,70 @@
+id: pd-nuclei-auth-test
+
+info:
+ name: ProjectDiscovery Test Dev Servers
+ author: pdteam
+ description: |
+ This is a auth file for ProjectDiscovery dev servers.
+ It contains auth data of all projectdiscovery dev servers.
+
+# Note: this is a dummy example file. none of the secrets here are real.
+
+# static secrets
+static:
+ # for header based auth session
+ - type: header
+ domains:
+ - api.projectdiscovery.io
+ - cve.projectdiscovery.io
+ - chaos.projectdiscovery.io
+ headers:
+ - key: x-pdcp-key
+ value:
+
+ # for query based auth session
+ - type: Query
+ domains:
+ - scanme.sh
+ params:
+ - key: token
+ value: 1a2b3c4d5e6f7g8h9i0j
+
+ # for cookie based auth session
+ - type: Cookie
+ domains:
+ - scanme.sh
+ cookies:
+ - key: PHPSESSID
+ value: 1a2b3c4d5e6f7g8h9i0j
+
+ # for basic auth session
+ - type: BasicAuth
+ domains:
+ - scanme.sh
+ username: test
+ password: test
+
+ # for authorization bearer token
+ - type: BearerToken
+ domains-regex:
+ - .*scanme.sh
+ - .*pdtm.sh
+ token: test
+
+
+# dynamic secrets (powered by nuclei-templates)
+dynamic:
+ - template: /path/to/wordpress-login.yaml
+ variables:
+ - name: username
+ value: pdteam
+ - name: password
+ value: nuclei-v3.2.0
+ type: Cookie
+ domains:
+ - localhost:8080
+ cookies:
+ - raw: "{{wp-global-cookie}}"
+ - raw: "{{wp-admin-cookie}}"
+ - raw: "{{wp-plugin-cookie}}"
+
diff --git a/pkg/authprovider/file.go b/pkg/authprovider/file.go
new file mode 100644
index 0000000000..77f45e40fb
--- /dev/null
+++ b/pkg/authprovider/file.go
@@ -0,0 +1,168 @@
+package authprovider
+
+import (
+ "net"
+ "net/url"
+ "regexp"
+ "strings"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx"
+ errorutil "github.com/projectdiscovery/utils/errors"
+ urlutil "github.com/projectdiscovery/utils/url"
+)
+
+// FileAuthProvider is an auth provider for file based auth
+// it accepts a secrets file and returns its provider
+type FileAuthProvider struct {
+ Path string
+ store *authx.Authx
+ compiled map[*regexp.Regexp]authx.AuthStrategy
+ domains map[string]authx.AuthStrategy
+}
+
+// NewFileAuthProvider creates a new file based auth provider
+func NewFileAuthProvider(path string, callback authx.LazyFetchSecret) (AuthProvider, error) {
+ store, err := authx.GetAuthDataFromFile(path)
+ if err != nil {
+ return nil, err
+ }
+ if len(store.Secrets) == 0 && len(store.Dynamic) == 0 {
+ return nil, ErrNoSecrets
+ }
+ if len(store.Dynamic) > 0 && callback == nil {
+ return nil, errorutil.New("lazy fetch callback is required for dynamic secrets")
+ }
+ for _, secret := range store.Secrets {
+ if err := secret.Validate(); err != nil {
+ return nil, errorutil.NewWithErr(err).Msgf("invalid secret in file: %s", path)
+ }
+ }
+ for i, dynamic := range store.Dynamic {
+ if err := dynamic.Validate(); err != nil {
+ return nil, errorutil.NewWithErr(err).Msgf("invalid dynamic in file: %s", path)
+ }
+ dynamic.SetLazyFetchCallback(callback)
+ store.Dynamic[i] = dynamic
+ }
+ f := &FileAuthProvider{Path: path, store: store}
+ f.init()
+ return f, nil
+}
+
+// init initializes the file auth provider
+func (f *FileAuthProvider) init() {
+ for _, secret := range f.store.Secrets {
+ if len(secret.DomainsRegex) > 0 {
+ for _, domain := range secret.DomainsRegex {
+ if f.compiled == nil {
+ f.compiled = make(map[*regexp.Regexp]authx.AuthStrategy)
+ }
+ compiled, err := regexp.Compile(domain)
+ if err != nil {
+ continue
+ }
+ f.compiled[compiled] = secret.GetStrategy()
+ }
+ }
+ for _, domain := range secret.Domains {
+ if f.domains == nil {
+ f.domains = make(map[string]authx.AuthStrategy)
+ }
+ f.domains[strings.TrimSpace(domain)] = secret.GetStrategy()
+ if strings.HasSuffix(domain, ":80") {
+ f.domains[strings.TrimSuffix(domain, ":80")] = secret.GetStrategy()
+ }
+ if strings.HasSuffix(domain, ":443") {
+ f.domains[strings.TrimSuffix(domain, ":443")] = secret.GetStrategy()
+ }
+ }
+ }
+ for _, dynamic := range f.store.Dynamic {
+ if len(dynamic.DomainsRegex) > 0 {
+ for _, domain := range dynamic.DomainsRegex {
+ if f.compiled == nil {
+ f.compiled = make(map[*regexp.Regexp]authx.AuthStrategy)
+ }
+ compiled, err := regexp.Compile(domain)
+ if err != nil {
+ continue
+ }
+ f.compiled[compiled] = &authx.DynamicAuthStrategy{Dynamic: dynamic}
+ }
+ }
+ for _, domain := range dynamic.Domains {
+ if f.domains == nil {
+ f.domains = make(map[string]authx.AuthStrategy)
+ }
+ f.domains[strings.TrimSpace(domain)] = &authx.DynamicAuthStrategy{Dynamic: dynamic}
+ if strings.HasSuffix(domain, ":80") {
+ f.domains[strings.TrimSuffix(domain, ":80")] = &authx.DynamicAuthStrategy{Dynamic: dynamic}
+ }
+ if strings.HasSuffix(domain, ":443") {
+ f.domains[strings.TrimSuffix(domain, ":443")] = &authx.DynamicAuthStrategy{Dynamic: dynamic}
+ }
+ }
+ }
+}
+
+// LookupAddr looks up a given domain/address and returns appropriate auth strategy
+func (f *FileAuthProvider) LookupAddr(addr string) authx.AuthStrategy {
+ if strings.Contains(addr, ":") {
+ // default normalization for host:port
+ host, port, err := net.SplitHostPort(addr)
+ if err == nil && (port == "80" || port == "443") {
+ addr = host
+ }
+ }
+ for domain, strategy := range f.domains {
+ if strings.EqualFold(domain, addr) {
+ return strategy
+ }
+ }
+ for compiled, strategy := range f.compiled {
+ if compiled.MatchString(addr) {
+ return strategy
+ }
+ }
+ return nil
+}
+
+// LookupURL looks up a given URL and returns appropriate auth strategy
+func (f *FileAuthProvider) LookupURL(u *url.URL) authx.AuthStrategy {
+ return f.LookupAddr(u.Host)
+}
+
+// LookupURLX looks up a given URL and returns appropriate auth strategy
+func (f *FileAuthProvider) LookupURLX(u *urlutil.URL) authx.AuthStrategy {
+ return f.LookupAddr(u.Host)
+}
+
+// GetTemplatePaths returns the template path for the auth provider
+func (f *FileAuthProvider) GetTemplatePaths() []string {
+ res := []string{}
+ for _, dynamic := range f.store.Dynamic {
+ if dynamic.TemplatePath != "" {
+ res = append(res, dynamic.TemplatePath)
+ }
+ }
+ return res
+}
+
+// PreFetchSecrets pre-fetches the secrets from the auth provider
+func (f *FileAuthProvider) PreFetchSecrets() error {
+ for _, s := range f.domains {
+ if val, ok := s.(*authx.DynamicAuthStrategy); ok {
+ if err := val.Dynamic.Fetch(false); err != nil {
+ return err
+ }
+ }
+ }
+ for _, s := range f.compiled {
+ if val, ok := s.(*authx.DynamicAuthStrategy); ok {
+ if err := val.Dynamic.Fetch(false); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
diff --git a/pkg/authprovider/interface.go b/pkg/authprovider/interface.go
new file mode 100644
index 0000000000..b21668fceb
--- /dev/null
+++ b/pkg/authprovider/interface.go
@@ -0,0 +1,59 @@
+package authprovider
+
+import (
+ "fmt"
+ "net/url"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx"
+ urlutil "github.com/projectdiscovery/utils/url"
+)
+
+var (
+ ErrNoSecrets = fmt.Errorf("no secrets in given provider")
+)
+
+var (
+ _ AuthProvider = &FileAuthProvider{}
+)
+
+// AuthProvider is an interface for auth providers
+// It implements a data structure suitable for quick lookup and retrieval
+// of auth strategies
+type AuthProvider interface {
+ // LookupAddr looks up a given domain/address and returns appropriate auth strategy
+ // for it (accepted inputs are scanme.sh or scanme.sh:443)
+ LookupAddr(string) authx.AuthStrategy
+ // LookupURL looks up a given URL and returns appropriate auth strategy
+ // it accepts a valid url struct and returns the auth strategy
+ LookupURL(*url.URL) authx.AuthStrategy
+ // LookupURLX looks up a given URL and returns appropriate auth strategy
+ // it accepts pd url struct (i.e urlutil.URL) and returns the auth strategy
+ LookupURLX(*urlutil.URL) authx.AuthStrategy
+ // GetTemplatePaths returns the template path for the auth provider
+ // that will be used for dynamic secret fetching
+ GetTemplatePaths() []string
+ // PreFetchSecrets pre-fetches the secrets from the auth provider
+ // instead of lazy fetching
+ PreFetchSecrets() error
+}
+
+// AuthProviderOptions contains options for the auth provider
+type AuthProviderOptions struct {
+ // File based auth provider options
+ SecretsFiles []string
+ // LazyFetchSecret is a callback for lazy fetching of dynamic secrets
+ LazyFetchSecret authx.LazyFetchSecret
+}
+
+// NewAuthProvider creates a new auth provider from the given options
+func NewAuthProvider(options *AuthProviderOptions) (AuthProvider, error) {
+ var providers []AuthProvider
+ for _, file := range options.SecretsFiles {
+ provider, err := NewFileAuthProvider(file, options.LazyFetchSecret)
+ if err != nil {
+ return nil, err
+ }
+ providers = append(providers, provider)
+ }
+ return NewMultiAuthProvider(providers...), nil
+}
diff --git a/pkg/authprovider/multi.go b/pkg/authprovider/multi.go
new file mode 100644
index 0000000000..2e9b19df8c
--- /dev/null
+++ b/pkg/authprovider/multi.go
@@ -0,0 +1,67 @@
+package authprovider
+
+import (
+ "net/url"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx"
+ urlutil "github.com/projectdiscovery/utils/url"
+)
+
+// MultiAuthProvider is a convenience wrapper for multiple auth providers
+// it returns the first matching auth strategy for a given domain
+// if there are multiple auth strategies for a given domain, it returns the first one
+type MultiAuthProvider struct {
+ Providers []AuthProvider
+}
+
+// NewMultiAuthProvider creates a new multi auth provider
+func NewMultiAuthProvider(providers ...AuthProvider) AuthProvider {
+ return &MultiAuthProvider{Providers: providers}
+}
+
+func (m *MultiAuthProvider) LookupAddr(host string) authx.AuthStrategy {
+ for _, provider := range m.Providers {
+ strategy := provider.LookupAddr(host)
+ if strategy != nil {
+ return strategy
+ }
+ }
+ return nil
+}
+
+func (m *MultiAuthProvider) LookupURL(u *url.URL) authx.AuthStrategy {
+ for _, provider := range m.Providers {
+ strategy := provider.LookupURL(u)
+ if strategy != nil {
+ return strategy
+ }
+ }
+ return nil
+}
+
+func (m *MultiAuthProvider) LookupURLX(u *urlutil.URL) authx.AuthStrategy {
+ for _, provider := range m.Providers {
+ strategy := provider.LookupURLX(u)
+ if strategy != nil {
+ return strategy
+ }
+ }
+ return nil
+}
+
+func (m *MultiAuthProvider) GetTemplatePaths() []string {
+ var res []string
+ for _, provider := range m.Providers {
+ res = append(res, provider.GetTemplatePaths()...)
+ }
+ return res
+}
+
+func (m *MultiAuthProvider) PreFetchSecrets() error {
+ for _, provider := range m.Providers {
+ if err := provider.PreFetchSecrets(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/pkg/catalog/config/constants.go b/pkg/catalog/config/constants.go
index 8fa104301b..06d4a1ccca 100644
--- a/pkg/catalog/config/constants.go
+++ b/pkg/catalog/config/constants.go
@@ -6,6 +6,20 @@ import (
"github.com/Masterminds/semver/v3"
)
+type AppMode string
+
+const (
+ AppModeLibrary AppMode = "library"
+ AppModeCLI AppMode = "cli"
+)
+
+var (
+ // Global Var to control behaviours specific to cli or library
+ // maybe this should be moved to utils ??
+ // this is overwritten in cmd/nuclei/main.go
+ CurrentAppMode = AppModeLibrary
+)
+
const (
TemplateConfigFileName = ".templates-config.json"
NucleiTemplatesDirName = "nuclei-templates"
@@ -17,7 +31,7 @@ const (
CLIConfigFileName = "config.yaml"
ReportingConfigFilename = "reporting-config.yaml"
// Version is the current version of nuclei
- Version = `v3.1.10`
+ Version = `v3.2.0`
// Directory Names of custom templates
CustomS3TemplatesDirName = "s3"
CustomGitHubTemplatesDirName = "github"
diff --git a/pkg/catalog/config/nucleiconfig.go b/pkg/catalog/config/nucleiconfig.go
index 5ff7e8c5d9..bb9008b5e2 100644
--- a/pkg/catalog/config/nucleiconfig.go
+++ b/pkg/catalog/config/nucleiconfig.go
@@ -45,6 +45,9 @@ type Config struct {
LatestNucleiTemplatesVersion string `json:"nuclei-templates-latest-version"`
LatestNucleiIgnoreHash string `json:"nuclei-latest-ignore-hash,omitempty"`
+ // Other AppLevel/Global Settings
+ registerdCaches []GlobalCache `json:"-"` // registered global caches
+
// internal / unexported fields
disableUpdates bool `json:"-"` // disable updates both version check and template updates
homeDir string `json:"-"` // User Home Directory
@@ -298,6 +301,19 @@ func (c *Config) WriteTemplatesIndex(index map[string]string) error {
return os.WriteFile(indexFile, buff.Bytes(), 0600)
}
+// RegisterGlobalCache registers a global cache at app level
+// and is available to be purged on demand
+func (c *Config) RegisterGlobalCache(cache GlobalCache) {
+ c.registerdCaches = append(c.registerdCaches, cache)
+}
+
+// PurgeGlobalCache purges all registered global caches
+func (c *Config) PurgeGlobalCache() {
+ for _, cache := range c.registerdCaches {
+ cache.Purge()
+ }
+}
+
// getTemplatesConfigFilePath returns configDir/.templates-config.json file path
func (c *Config) getTemplatesConfigFilePath() string {
return filepath.Join(c.configDir, TemplateConfigFileName)
diff --git a/pkg/catalog/config/template.go b/pkg/catalog/config/template.go
index 2b7ea83ed0..ff78ef09eb 100644
--- a/pkg/catalog/config/template.go
+++ b/pkg/catalog/config/template.go
@@ -13,6 +13,13 @@ import (
stringsutil "github.com/projectdiscovery/utils/strings"
)
+// GlobalCache are global cache that have global
+// scope and are not purged but can be purged
+// via config.DefaultConfig
+type GlobalCache interface {
+ Purge()
+}
+
var knownConfigFiles = []string{"cves.json", "contributors.json", "TEMPLATES-STATS.json"}
// TemplateFormat
diff --git a/pkg/catalog/loader/filter/tag_filter.go b/pkg/catalog/loader/filter/tag_filter.go
index 119b045b9d..add70e2dbe 100644
--- a/pkg/catalog/loader/filter/tag_filter.go
+++ b/pkg/catalog/loader/filter/tag_filter.go
@@ -408,7 +408,8 @@ func New(config *Config) (*TagFilter, error) {
if _, ok := filter.allowedTags[val]; !ok {
filter.allowedTags[val] = struct{}{}
}
- delete(filter.block, val)
+ // Note: only tags specified in IncludeTags should be removed from the block list
+ // not normal tags like config.Tags
}
}
for _, tag := range config.IncludeTags {
diff --git a/pkg/catalog/loader/filter/tag_filter_test.go b/pkg/catalog/loader/filter/tag_filter_test.go
index ad14ddf80c..c73d654903 100644
--- a/pkg/catalog/loader/filter/tag_filter_test.go
+++ b/pkg/catalog/loader/filter/tag_filter_test.go
@@ -85,7 +85,7 @@ func TestTagBasedFilter(t *testing.T) {
})
t.Run("match-includes", func(t *testing.T) {
filter, err := New(&Config{
- Tags: []string{"fuzz"},
+ IncludeTags: []string{"fuzz"},
ExcludeTags: []string{"fuzz"},
})
require.Nil(t, err)
diff --git a/pkg/catalog/loader/loader.go b/pkg/catalog/loader/loader.go
index ec9669fc8f..4c35d31fa5 100644
--- a/pkg/catalog/loader/loader.go
+++ b/pkg/catalog/loader/loader.go
@@ -13,7 +13,6 @@ import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
- cfg "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader/filter"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/parsers"
@@ -62,6 +61,8 @@ type Config struct {
Catalog catalog.Catalog
ExecutorOptions protocols.ExecutorOptions
+
+ OnlyLoadHTTPFuzzing bool
}
// Store is a storage for loaded nuclei templates
@@ -111,19 +112,19 @@ func NewConfig(options *types.Options, catalog catalog.Catalog, executerOpts pro
}
// New creates a new template store based on provided configuration
-func New(config *Config) (*Store, error) {
+func New(cfg *Config) (*Store, error) {
tagFilter, err := filter.New(&filter.Config{
- Tags: config.Tags,
- ExcludeTags: config.ExcludeTags,
- Authors: config.Authors,
- Severities: config.Severities,
- ExcludeSeverities: config.ExcludeSeverities,
- IncludeTags: config.IncludeTags,
- IncludeIds: config.IncludeIds,
- ExcludeIds: config.ExcludeIds,
- Protocols: config.Protocols,
- ExcludeProtocols: config.ExcludeProtocols,
- IncludeConditions: config.IncludeConditions,
+ Tags: cfg.Tags,
+ ExcludeTags: cfg.ExcludeTags,
+ Authors: cfg.Authors,
+ Severities: cfg.Severities,
+ ExcludeSeverities: cfg.ExcludeSeverities,
+ IncludeTags: cfg.IncludeTags,
+ IncludeIds: cfg.IncludeIds,
+ ExcludeIds: cfg.ExcludeIds,
+ Protocols: cfg.Protocols,
+ ExcludeProtocols: cfg.ExcludeProtocols,
+ IncludeConditions: cfg.IncludeConditions,
})
if err != nil {
return nil, err
@@ -131,23 +132,23 @@ func New(config *Config) (*Store, error) {
// Create a tag filter based on provided configuration
store := &Store{
- config: config,
+ config: cfg,
tagFilter: tagFilter,
pathFilter: filter.NewPathFilter(&filter.PathFilterConfig{
- IncludedTemplates: config.IncludeTemplates,
- ExcludedTemplates: config.ExcludeTemplates,
- }, config.Catalog),
- finalTemplates: config.Templates,
- finalWorkflows: config.Workflows,
+ IncludedTemplates: cfg.IncludeTemplates,
+ ExcludedTemplates: cfg.ExcludeTemplates,
+ }, cfg.Catalog),
+ finalTemplates: cfg.Templates,
+ finalWorkflows: cfg.Workflows,
}
// Do a check to see if we have URLs in templates flag, if so
// we need to processs them separately and remove them from the initial list
var templatesFinal []string
- for _, template := range config.Templates {
+ for _, template := range cfg.Templates {
// TODO: Add and replace this with urlutil.IsURL() helper
if stringsutil.HasPrefixAny(template, httpPrefix, httpsPrefix) {
- config.TemplateURLs = append(config.TemplateURLs, template)
+ cfg.TemplateURLs = append(cfg.TemplateURLs, template)
} else {
templatesFinal = append(templatesFinal, template)
}
@@ -155,7 +156,7 @@ func New(config *Config) (*Store, error) {
// fix editor paths
remoteTemplates := []string{}
- for _, v := range config.TemplateURLs {
+ for _, v := range cfg.TemplateURLs {
if _, err := urlutil.Parse(v); err == nil {
remoteTemplates = append(remoteTemplates, handleTemplatesEditorURLs(v))
} else {
@@ -163,12 +164,12 @@ func New(config *Config) (*Store, error) {
templatesFinal = append(templatesFinal, v) // something went wrong, treat it as a file
}
}
- config.TemplateURLs = remoteTemplates
+ cfg.TemplateURLs = remoteTemplates
store.finalTemplates = templatesFinal
- urlBasedTemplatesProvided := len(config.TemplateURLs) > 0 || len(config.WorkflowURLs) > 0
+ urlBasedTemplatesProvided := len(cfg.TemplateURLs) > 0 || len(cfg.WorkflowURLs) > 0
if urlBasedTemplatesProvided {
- remoteTemplates, remoteWorkflows, err := getRemoteTemplatesAndWorkflows(config.TemplateURLs, config.WorkflowURLs, config.RemoteTemplateDomainList)
+ remoteTemplates, remoteWorkflows, err := getRemoteTemplatesAndWorkflows(cfg.TemplateURLs, cfg.WorkflowURLs, cfg.RemoteTemplateDomainList)
if err != nil {
return store, err
}
@@ -186,7 +187,7 @@ func New(config *Config) (*Store, error) {
}
// Handle a case with no templates or workflows, where we use base directory
if len(store.finalTemplates) == 0 && len(store.finalWorkflows) == 0 && !urlBasedTemplatesProvided {
- store.finalTemplates = []string{cfg.DefaultConfig.TemplatesDirectory}
+ store.finalTemplates = []string{config.DefaultConfig.TemplatesDirectory}
}
return store, nil
@@ -397,6 +398,11 @@ 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 !parsed.Verified && store.config.ExecutorOptions.Options.DisableUnsignedTemplates {
+ // skip unverified templates when prompted to
+ stats.Increment(parsers.SkippedUnsignedStats)
+ continue
+ }
if len(parsed.RequestsHeadless) > 0 && !store.config.ExecutorOptions.Options.Headless {
// donot include headless template in final list if headless flag is not set
stats.Increment(parsers.HeadlessFlagWarningStats)
@@ -411,10 +417,19 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ
}
} 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)
+ stats.Increment(parsers.UnsignedCodeWarning)
+ // these will be skipped so increment skip counter
+ stats.Increment(parsers.SkippedUnsignedStats)
if config.DefaultConfig.LogAllEvents {
gologger.Print().Msgf("[%v] Tampered/Unsigned template at %v.\n", aurora.Yellow("WRN").String(), templatePath)
}
+ } else if parsed.IsFuzzing() && !store.config.ExecutorOptions.Options.FuzzTemplates {
+ stats.Increment(parsers.FuzzFlagWarningStats)
+ if config.DefaultConfig.LogAllEvents {
+ gologger.Print().Msgf("[%v] Fuzz flag is required for fuzzing template '%s'.\n", aurora.Yellow("WRN").String(), templatePath)
+ }
+ } else if store.config.OnlyLoadHTTPFuzzing && !parsed.IsFuzzing() {
+ gologger.Warning().Msgf("Non-Fuzzing template '%s' can only be run on list input mode targets\n", templatePath)
} else {
loadedTemplates = append(loadedTemplates, parsed)
}
diff --git a/pkg/core/engine.go b/pkg/core/engine.go
index b9a8036a0f..93915bc2c6 100644
--- a/pkg/core/engine.go
+++ b/pkg/core/engine.go
@@ -3,7 +3,6 @@ package core
import (
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
@@ -22,21 +21,6 @@ type Engine struct {
Callback func(*output.ResultEvent) // Executed on results
}
-// InputProvider is an input providing interface for the nuclei execution
-// engine.
-//
-// An example InputProvider implementation is provided in form of hybrid
-// input provider in pkg/core/inputs/hybrid/hmap.go
-type InputProvider interface {
- // Count returns the number of items for input provider
- Count() int64
- // Scan iterates the input and each found item is passed to the
- // callback consumer.
- Scan(callback func(value *contextargs.MetaInput) bool)
- // Set adds item to input provider
- Set(value string)
-}
-
// New returns a new Engine instance
func New(options *types.Options) *Engine {
engine := &Engine{
diff --git a/pkg/core/execute_options.go b/pkg/core/execute_options.go
index 50c61de3ad..fd1fadae3d 100644
--- a/pkg/core/execute_options.go
+++ b/pkg/core/execute_options.go
@@ -7,6 +7,7 @@ import (
"github.com/remeh/sizedwaitgroup"
"github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
@@ -20,18 +21,18 @@ import (
//
// All the execution logic for the templates/workflows happens in this part
// of the engine.
-func (e *Engine) Execute(templates []*templates.Template, target InputProvider) *atomic.Bool {
+func (e *Engine) Execute(templates []*templates.Template, target provider.InputProvider) *atomic.Bool {
return e.ExecuteScanWithOpts(templates, target, false)
}
// ExecuteWithResults a list of templates with results
-func (e *Engine) ExecuteWithResults(templatesList []*templates.Template, target InputProvider, callback func(*output.ResultEvent)) *atomic.Bool {
+func (e *Engine) ExecuteWithResults(templatesList []*templates.Template, target provider.InputProvider, callback func(*output.ResultEvent)) *atomic.Bool {
e.Callback = callback
return e.ExecuteScanWithOpts(templatesList, target, false)
}
// ExecuteScanWithOpts executes scan with given scanStrategy
-func (e *Engine) ExecuteScanWithOpts(templatesList []*templates.Template, target InputProvider, noCluster bool) *atomic.Bool {
+func (e *Engine) ExecuteScanWithOpts(templatesList []*templates.Template, target provider.InputProvider, noCluster bool) *atomic.Bool {
results := &atomic.Bool{}
selfcontainedWg := &sync.WaitGroup{}
@@ -100,7 +101,7 @@ func (e *Engine) ExecuteScanWithOpts(templatesList []*templates.Template, target
}
// executeTemplateSpray executes scan using template spray strategy where targets are iterated over each template
-func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, target InputProvider) *atomic.Bool {
+func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool {
results := &atomic.Bool{}
// wp is workpool that contains different waitgroups for
@@ -131,11 +132,11 @@ func (e *Engine) executeTemplateSpray(templatesList []*templates.Template, targe
}
// executeHostSpray executes scan using host spray strategy where templates are iterated over each target
-func (e *Engine) executeHostSpray(templatesList []*templates.Template, target InputProvider) *atomic.Bool {
+func (e *Engine) executeHostSpray(templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool {
results := &atomic.Bool{}
wp := sizedwaitgroup.New(e.options.BulkSize + e.options.HeadlessBulkSize)
- target.Scan(func(value *contextargs.MetaInput) bool {
+ target.Iterate(func(value *contextargs.MetaInput) bool {
wp.Add()
go func(targetval *contextargs.MetaInput) {
defer wp.Done()
diff --git a/pkg/core/executors.go b/pkg/core/executors.go
index 81baa020dc..b491bd8e0b 100644
--- a/pkg/core/executors.go
+++ b/pkg/core/executors.go
@@ -5,6 +5,7 @@ import (
"sync/atomic"
"github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
@@ -44,7 +45,7 @@ func (e *Engine) executeAllSelfContained(alltemplates []*templates.Template, res
}
// executeTemplateWithTarget executes a given template on x targets (with a internal targetpool(i.e concurrency))
-func (e *Engine) executeTemplateWithTargets(template *templates.Template, target InputProvider, results *atomic.Bool) {
+func (e *Engine) executeTemplateWithTargets(template *templates.Template, target provider.InputProvider, results *atomic.Bool) {
// this is target pool i.e max target to execute
wg := e.workPool.InputPool(template.Type())
@@ -75,7 +76,7 @@ func (e *Engine) executeTemplateWithTargets(template *templates.Template, target
currentInfo.Unlock()
}
- target.Scan(func(scannedValue *contextargs.MetaInput) bool {
+ target.Iterate(func(scannedValue *contextargs.MetaInput) bool {
// Best effort to track the host progression
// skips indexes lower than the minimum in-flight at interruption time
var skip bool
diff --git a/pkg/core/inputs/inputs.go b/pkg/core/inputs/inputs.go
deleted file mode 100644
index 3e8e456b07..0000000000
--- a/pkg/core/inputs/inputs.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package inputs
-
-import (
- "github.com/projectdiscovery/httpx/common/httpx"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
- "github.com/projectdiscovery/nuclei/v3/pkg/utils"
-)
-
-type SimpleInputProvider struct {
- Inputs []*contextargs.MetaInput
-}
-
-// Count returns the number of items for input provider
-func (s *SimpleInputProvider) Count() int64 {
- return int64(len(s.Inputs))
-}
-
-// Scan calls a callback function till the input provider is exhausted
-func (s *SimpleInputProvider) Scan(callback func(value *contextargs.MetaInput) bool) {
- for _, v := range s.Inputs {
- if !callback(v) {
- return
- }
- }
-}
-
-// Set adds item to input provider
-func (s *SimpleInputProvider) Set(value string) {
- s.Inputs = append(s.Inputs, &contextargs.MetaInput{Input: value})
-}
-
-// SetWithProbe adds item to input provider with http probing
-func (s *SimpleInputProvider) SetWithProbe(value string, httpxClient *httpx.HTTPX) {
- valueToAppend := value
- if result := utils.ProbeURL(value, httpxClient); result != "" {
- valueToAppend = result
- }
- s.Inputs = append(s.Inputs, &contextargs.MetaInput{Input: valueToAppend})
-}
diff --git a/pkg/fuzz/component/body.go b/pkg/fuzz/component/body.go
new file mode 100644
index 0000000000..79d8d6a881
--- /dev/null
+++ b/pkg/fuzz/component/body.go
@@ -0,0 +1,140 @@
+package component
+
+import (
+ "bytes"
+ "context"
+ "io"
+ "strconv"
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat"
+ "github.com/projectdiscovery/retryablehttp-go"
+ readerutil "github.com/projectdiscovery/utils/reader"
+)
+
+// Body is a component for a request body
+type Body struct {
+ value *Value
+
+ req *retryablehttp.Request
+}
+
+var _ Component = &Body{}
+
+// NewBody creates a new body component
+func NewBody() *Body {
+ return &Body{}
+}
+
+// Name returns the name of the component
+func (b *Body) Name() string {
+ return RequestBodyComponent
+}
+
+// Parse parses the component and returns the
+// parsed component
+func (b *Body) Parse(req *retryablehttp.Request) (bool, error) {
+ if req.Body == nil {
+ return false, nil
+ }
+ b.req = req
+
+ contentType := req.Header.Get("Content-Type")
+
+ data, err := io.ReadAll(req.Body)
+ if err != nil {
+ return false, errors.Wrap(err, "could not read body")
+ }
+ req.Body = io.NopCloser(bytes.NewReader(data))
+ dataStr := string(data)
+
+ if dataStr == "" {
+ return false, nil
+ }
+
+ b.value = NewValue(dataStr)
+ if b.value.Parsed() != nil {
+ return true, nil
+ }
+
+ switch {
+ case strings.Contains(contentType, "application/json") && b.value.Parsed() == nil:
+ return b.parseBody(dataformat.JSONDataFormat, req)
+ case strings.Contains(contentType, "application/xml") && b.value.Parsed() == nil:
+ return b.parseBody(dataformat.XMLDataFormat, req)
+ case strings.Contains(contentType, "multipart/form-data") && b.value.Parsed() == nil:
+ return b.parseBody(dataformat.MultiPartFormDataFormat, req)
+ }
+ parsed, err := b.parseBody(dataformat.FormDataFormat, req)
+ if err != nil {
+ gologger.Warning().Msgf("Could not parse body as form data: %s\n", err)
+ return b.parseBody(dataformat.RawDataFormat, req)
+ }
+ return parsed, err
+}
+
+// parseBody parses a body with a custom decoder
+func (b *Body) parseBody(decoderName string, req *retryablehttp.Request) (bool, error) {
+ decoder := dataformat.Get(decoderName)
+ if decoderName == dataformat.MultiPartFormDataFormat {
+ // set content type to extract boundary
+ if err := decoder.(*dataformat.MultiPartForm).ParseBoundary(req.Header.Get("Content-Type")); err != nil {
+ return false, errors.Wrap(err, "could not parse boundary")
+ }
+ }
+ decoded, err := decoder.Decode(b.value.String())
+ if err != nil {
+ return false, errors.Wrap(err, "could not decode raw")
+ }
+ b.value.SetParsed(decoded, decoder.Name())
+ return true, nil
+}
+
+// Iterate iterates through the component
+func (b *Body) Iterate(callback func(key string, value interface{}) error) error {
+ for key, value := range b.value.Parsed() {
+ if strings.HasPrefix(key, "#_") {
+ continue
+ }
+ if err := callback(key, value); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// SetValue sets a value in the component
+func (b *Body) SetValue(key string, value string) error {
+ if !b.value.SetParsedValue(key, value) {
+ return ErrSetValue
+ }
+ return nil
+}
+
+// Delete deletes a key from the component
+func (b *Body) Delete(key string) error {
+ if !b.value.Delete(key) {
+ return ErrKeyNotFound
+ }
+ return nil
+}
+
+// Rebuild returns a new request with the
+// component rebuilt
+func (b *Body) Rebuild() (*retryablehttp.Request, error) {
+ encoded, err := b.value.Encode()
+ if err != nil {
+ return nil, errors.Wrap(err, "could not encode body")
+ }
+ cloned := b.req.Clone(context.Background())
+ reusableReader, err := readerutil.NewReusableReadCloser(encoded)
+ if err != nil {
+ return nil, errors.Wrap(err, "could not create reusable reader")
+ }
+ cloned.Body = reusableReader
+ cloned.ContentLength = int64(len(encoded))
+ cloned.Header.Set("Content-Length", strconv.Itoa(len(encoded)))
+ return cloned, nil
+}
diff --git a/pkg/fuzz/component/body_test.go b/pkg/fuzz/component/body_test.go
new file mode 100644
index 0000000000..42e7e405d3
--- /dev/null
+++ b/pkg/fuzz/component/body_test.go
@@ -0,0 +1,175 @@
+package component
+
+import (
+ "bytes"
+ "io"
+ "mime/multipart"
+ "net/url"
+ "strings"
+ "testing"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+ "github.com/stretchr/testify/require"
+)
+
+func TestBodyComponent(t *testing.T) {
+ req, err := retryablehttp.NewRequest("POST", "https://example.com", strings.NewReader(`{"foo":"bar"}`))
+ if err != nil {
+ t.Fatal(err)
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ body := New(RequestBodyComponent)
+ _, err = body.Parse(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var keys []string
+ var values []string
+ _ = body.Iterate(func(key string, value interface{}) error {
+ keys = append(keys, key)
+ values = append(values, value.(string))
+ return nil
+ })
+
+ require.Equal(t, []string{"foo"}, keys, "unexpected keys")
+ require.Equal(t, []string{"bar"}, values, "unexpected values")
+
+ _ = body.SetValue("foo", "baz")
+
+ rebuilt, err := body.Rebuild()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ newBody, err := io.ReadAll(rebuilt.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ require.Equal(t, `{"foo":"baz"}`, string(newBody), "unexpected body")
+}
+
+func TestBodyXMLComponent(t *testing.T) {
+ var body = "1 1 "
+
+ req, err := retryablehttp.NewRequest("POST", "https://example.com", strings.NewReader(body))
+ if err != nil {
+ t.Fatal(err)
+ }
+ req.Header.Set("Content-Type", "application/xml")
+
+ bodyComponent := New(RequestBodyComponent)
+ parsed, err := bodyComponent.Parse(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ require.True(t, parsed, "could not parse body")
+
+ _ = bodyComponent.SetValue("stockCheck~productId", "2'6842")
+ rebuilt, err := bodyComponent.Rebuild()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ newBody, err := io.ReadAll(rebuilt.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ require.Equal(t, "2'6842 1 ", string(newBody), "unexpected body")
+}
+
+func TestBodyFormComponent(t *testing.T) {
+ formData := url.Values{}
+ formData.Set("key1", "value1")
+ formData.Set("key2", "value2")
+
+ req, err := retryablehttp.NewRequest("POST", "https://example.com", strings.NewReader(formData.Encode()))
+ if err != nil {
+ t.Fatal(err)
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ body := New(RequestBodyComponent)
+ _, err = body.Parse(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var keys []string
+ var values []string
+ _ = body.Iterate(func(key string, value interface{}) error {
+ keys = append(keys, key)
+ values = append(values, value.(string))
+ return nil
+ })
+
+ require.ElementsMatch(t, []string{"key1", "key2"}, keys, "unexpected keys")
+ require.ElementsMatch(t, []string{"value1", "value2"}, values, "unexpected values")
+
+ _ = body.SetValue("key1", "updatedValue1")
+
+ rebuilt, err := body.Rebuild()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ newBody, err := io.ReadAll(rebuilt.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ require.Equal(t, "key1=updatedValue1&key2=value2", string(newBody), "unexpected body")
+}
+
+func TestMultiPartFormComponent(t *testing.T) {
+ formData := &bytes.Buffer{}
+ writer := multipart.NewWriter(formData)
+
+ // Hypothetical form fields
+ _ = writer.WriteField("username", "testuser")
+ _ = writer.WriteField("password", "testpass")
+
+ contentType := writer.FormDataContentType()
+ _ = writer.Close()
+
+ req, err := retryablehttp.NewRequest("POST", "https://example.com", formData)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req.Header.Set("Content-Type", contentType)
+
+ body := New(RequestBodyComponent)
+ _, err = body.Parse(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var keys []string
+ var values []string
+ _ = body.Iterate(func(key string, value interface{}) error {
+ keys = append(keys, key)
+ values = append(values, value.(string))
+ return nil
+ })
+
+ require.ElementsMatch(t, []string{"username", "password"}, keys, "unexpected keys")
+ require.ElementsMatch(t, []string{"testuser", "testpass"}, values, "unexpected values")
+
+ // Update a value in the form
+ _ = body.SetValue("password", "updatedTestPass")
+
+ rebuilt, err := body.Rebuild()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ newBody, err := io.ReadAll(rebuilt.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check if the body contains the updated multipart form data
+ require.Contains(t, string(newBody), "updatedTestPass", "unexpected body content")
+ require.Contains(t, string(newBody), "username", "unexpected body content")
+ require.Contains(t, string(newBody), "testuser", "unexpected body content")
+}
diff --git a/pkg/fuzz/component/component.go b/pkg/fuzz/component/component.go
new file mode 100644
index 0000000000..5a8279c441
--- /dev/null
+++ b/pkg/fuzz/component/component.go
@@ -0,0 +1,95 @@
+package component
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/leslie-qiwa/flat"
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+// ErrSetValue is a error raised when a value cannot be set
+var ErrSetValue = errors.New("could not set value")
+
+func IsErrSetValue(err error) bool {
+ if err == nil {
+ return false
+ }
+ return strings.Contains(err.Error(), "could not set value")
+}
+
+// ErrKeyNotFound is a error raised when a key is not found
+var ErrKeyNotFound = errors.New("key not found")
+
+// Component is a component for a request
+type Component interface {
+ // Name returns the name of the component
+ Name() string
+ // Parse parses the component and returns the
+ // parsed component
+ Parse(req *retryablehttp.Request) (bool, error)
+ // Iterate iterates over all values of a component
+ // ex in case of query component, it will iterate over each query parameter
+ // depending on the rule if mode is single
+ // request is rebuilt for each value in this callback
+ // and in case of multiple, request will be rebuilt after iteration of all values
+ Iterate(func(key string, value interface{}) error) error
+ // SetValue sets a value in the component
+ // for a key
+ //
+ // After calling setValue for mutation, the value must be
+ // called again so as to reset the body to its original state.
+ SetValue(key string, value string) error
+ // Delete deletes a key from the component
+ // If it is applicable
+ Delete(key string) error
+ // Rebuild returns a new request with the
+ // component rebuilt
+ Rebuild() (*retryablehttp.Request, error)
+}
+
+const (
+ // RequestBodyComponent is the name of the request body component
+ RequestBodyComponent = "body"
+ // RequestQueryComponent is the name of the request query component
+ RequestQueryComponent = "query"
+ // RequestPathComponent is the name of the request url component
+ RequestPathComponent = "path"
+ // RequestHeaderComponent is the name of the request header component
+ RequestHeaderComponent = "header"
+ // RequestCookieComponent is the name of the request cookie component
+ RequestCookieComponent = "cookie"
+)
+
+// Components is a list of all available components
+var Components = []string{
+ RequestBodyComponent,
+ RequestQueryComponent,
+ RequestPathComponent,
+ RequestHeaderComponent,
+ RequestCookieComponent,
+}
+
+// New creates a new component for a componentType
+func New(componentType string) Component {
+ switch componentType {
+ case "body":
+ return NewBody()
+ case "query":
+ return NewQuery()
+ case "path":
+ return NewPath()
+ case "header":
+ return NewHeader()
+ case "cookie":
+ return NewCookie()
+ }
+ return nil
+}
+
+var (
+ flatOpts = &flat.Options{
+ Safe: true,
+ Delimiter: "~",
+ }
+)
diff --git a/pkg/fuzz/component/cookie.go b/pkg/fuzz/component/cookie.go
new file mode 100644
index 0000000000..7269284ccc
--- /dev/null
+++ b/pkg/fuzz/component/cookie.go
@@ -0,0 +1,138 @@
+package component
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+// Cookie is a component for a request cookie
+type Cookie struct {
+ value *Value
+
+ req *retryablehttp.Request
+}
+
+var _ Component = &Cookie{}
+
+// NewCookie creates a new cookie component
+func NewCookie() *Cookie {
+ return &Cookie{}
+}
+
+// Name returns the name of the component
+func (c *Cookie) Name() string {
+ return RequestCookieComponent
+}
+
+// Parse parses the component and returns the
+// parsed component
+func (c *Cookie) Parse(req *retryablehttp.Request) (bool, error) {
+ if len(req.Cookies()) == 0 {
+ return false, nil
+ }
+ c.req = req
+ c.value = NewValue("")
+
+ parsedCookies := make(map[string]interface{})
+ for _, cookie := range req.Cookies() {
+ parsedCookies[cookie.Name] = cookie.Value
+ }
+ if len(parsedCookies) == 0 {
+ return false, nil
+ }
+ c.value.SetParsed(parsedCookies, "")
+ return true, nil
+}
+
+// Iterate iterates through the component
+func (c *Cookie) Iterate(callback func(key string, value interface{}) error) error {
+ for key, value := range c.value.Parsed() {
+ // Skip ignored cookies
+ if _, ok := defaultIgnoredCookieKeys[key]; ok {
+ continue
+ }
+ if err := callback(key, value); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// SetValue sets a value in the component
+// for a key
+func (c *Cookie) SetValue(key string, value string) error {
+ if !c.value.SetParsedValue(key, value) {
+ return ErrSetValue
+ }
+ return nil
+}
+
+// Delete deletes a key from the component
+func (c *Cookie) Delete(key string) error {
+ if !c.value.Delete(key) {
+ return ErrKeyNotFound
+ }
+ return nil
+}
+
+// Rebuild returns a new request with the
+// component rebuilt
+func (c *Cookie) Rebuild() (*retryablehttp.Request, error) {
+ cloned := c.req.Clone(context.Background())
+
+ cloned.Header.Del("Cookie")
+ for key, value := range c.value.Parsed() {
+ cookie := &http.Cookie{
+ Name: key,
+ Value: value.(string), // Assume the value is always a string for cookies
+ }
+ cloned.AddCookie(cookie)
+ }
+ return cloned, nil
+}
+
+// A list of cookies that are essential to the request and
+// must not be fuzzed.
+var defaultIgnoredCookieKeys = map[string]struct{}{
+ "awsELB": {},
+ "AWSALB": {},
+ "AWSALBCORS": {},
+ "__utma": {},
+ "__utmb": {},
+ "__utmc": {},
+ "__utmt": {},
+ "__utmz": {},
+ "_ga": {},
+ "_gat": {},
+ "_gid": {},
+ "_gcl_au": {},
+ "_fbp": {},
+ "fr": {},
+ "__hstc": {},
+ "hubspotutk": {},
+ "__hssc": {},
+ "__hssrc": {},
+ "mp_mixpanel__c": {},
+ "JSESSIONID": {},
+ "NREUM": {},
+ "_pk_id": {},
+ "_pk_ref": {},
+ "_pk_ses": {},
+ "_pk_cvar": {},
+ "_pk_hsr": {},
+ "_hjIncludedInSample": {},
+ "__cfduid": {},
+ "cf_use_ob": {},
+ "cf_ob_info": {},
+ "intercom-session": {},
+ "optimizelyEndUserId": {},
+ "optimizelySegments": {},
+ "optimizelyBuckets": {},
+ "optimizelyPendingLogEvents": {},
+ "YSC": {},
+ "VISITOR_INFO1_LIVE": {},
+ "PREF": {},
+ "GPS": {},
+}
diff --git a/pkg/fuzz/component/cookie_test.go b/pkg/fuzz/component/cookie_test.go
new file mode 100644
index 0000000000..2e4ea94710
--- /dev/null
+++ b/pkg/fuzz/component/cookie_test.go
@@ -0,0 +1,57 @@
+package component
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+ "github.com/stretchr/testify/require"
+)
+
+func TestCookieComponent(t *testing.T) {
+ req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cookie := &http.Cookie{
+ Name: "session",
+ Value: "test-session",
+ }
+ req.AddCookie(cookie)
+
+ cookieComponent := NewCookie() // Assuming you have a function like this for creating a new cookie component
+ _, err = cookieComponent.Parse(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var cookieNames []string
+ var cookieValues []string
+ _ = cookieComponent.Iterate(func(key string, value interface{}) error {
+ cookieNames = append(cookieNames, key)
+ switch v := value.(type) {
+ case string:
+ cookieValues = append(cookieValues, v)
+ case []string:
+ cookieValues = append(cookieValues, v...)
+ }
+ return nil
+ })
+
+ require.Equal(t, []string{"session"}, cookieNames, "unexpected cookie names")
+ require.Equal(t, []string{"test-session"}, cookieValues, "unexpected cookie values")
+
+ err = cookieComponent.SetValue("session", "new-session")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rebuilt, err := cookieComponent.Rebuild()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Assuming the Rebuild function will reconstruct the entire request and also set the modified cookies
+ newCookie, _ := rebuilt.Cookie("session")
+ require.Equal(t, "new-session", newCookie.Value, "unexpected cookie value")
+}
diff --git a/pkg/fuzz/component/headers.go b/pkg/fuzz/component/headers.go
new file mode 100644
index 0000000000..60ac980480
--- /dev/null
+++ b/pkg/fuzz/component/headers.go
@@ -0,0 +1,186 @@
+package component
+
+import (
+ "context"
+ "strings"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+// Header is a component for a request header
+type Header struct {
+ value *Value
+
+ req *retryablehttp.Request
+}
+
+var _ Component = &Header{}
+
+// NewHeader creates a new header component
+func NewHeader() *Header {
+ return &Header{}
+}
+
+// Name returns the name of the component
+func (q *Header) Name() string {
+ return RequestHeaderComponent
+}
+
+// Parse parses the component and returns the
+// parsed component
+func (q *Header) Parse(req *retryablehttp.Request) (bool, error) {
+ q.req = req
+ q.value = NewValue("")
+
+ parsedHeaders := make(map[string]interface{})
+ for key, value := range req.Header {
+ if len(value) == 1 {
+ parsedHeaders[key] = value[0]
+ continue
+ }
+ parsedHeaders[key] = value
+ }
+ q.value.SetParsed(parsedHeaders, "")
+ return true, nil
+}
+
+// Iterate iterates through the component
+func (q *Header) Iterate(callback func(key string, value interface{}) error) error {
+ for key, value := range q.value.Parsed() {
+ // Skip ignored headers
+ if _, ok := defaultIgnoredHeaderKeys[key]; ok {
+ continue
+ }
+ if err := callback(key, value); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// SetValue sets a value in the component
+// for a key
+func (q *Header) SetValue(key string, value string) error {
+ if !q.value.SetParsedValue(key, value) {
+ return ErrSetValue
+ }
+ return nil
+}
+
+// Delete deletes a key from the component
+func (q *Header) Delete(key string) error {
+ if !q.value.Delete(key) {
+ return ErrKeyNotFound
+ }
+ return nil
+}
+
+// Rebuild returns a new request with the
+// component rebuilt
+func (q *Header) Rebuild() (*retryablehttp.Request, error) {
+ cloned := q.req.Clone(context.Background())
+ for key, value := range q.value.parsed {
+ if strings.EqualFold(key, "Host") {
+ cloned.Host = value.(string)
+ }
+ switch v := value.(type) {
+ case []interface{}:
+ for _, vv := range v {
+ if cloned.Header[key] == nil {
+ cloned.Header[key] = make([]string, 0)
+ }
+ cloned.Header[key] = append(cloned.Header[key], vv.(string))
+ }
+ case string:
+ cloned.Header[key] = []string{v}
+ }
+ }
+ return cloned, nil
+}
+
+// A list of headers that are essential to the request and
+// must not be fuzzed.
+var defaultIgnoredHeaderKeys = map[string]struct{}{
+ "Accept-Charset": {},
+ "Accept-Datetime": {},
+ "Accept-Encoding": {},
+ "Accept-Language": {},
+ "Accept": {},
+ "Access-Control-Request-Headers": {},
+ "Access-Control-Request-Method": {},
+ "Authorization": {},
+ "Cache-Control": {},
+ "Connection": {},
+ "Cookie": {},
+ "Content-Length": {},
+ "Content-Type": {},
+ "Date": {},
+ "Dnt": {},
+ "Expect": {},
+ "Forwarded": {},
+ "From": {},
+ "Host": {},
+ "If-Match": {},
+ "If-Modified-Since": {},
+ "If-None-Match": {},
+ "If-Range": {},
+ "If-Unmodified-Since": {},
+ "Max-Forwards": {},
+ "Pragma": {},
+ "Priority": {},
+ "Proxy-Authorization": {},
+ "Range": {},
+ "Sec-Ch-Ua": {},
+ "Sec-Ch-Ua-Mobile": {},
+ "Sec-Ch-Ua-Platform": {},
+ "Sec-Fetch-Dest": {},
+ "Sec-Fetch-Mode": {},
+ "Sec-Fetch-Site": {},
+ "Sec-Fetch-User": {},
+ "TE": {},
+ "Upgrade": {},
+ "Via": {},
+ "Warning": {},
+ "Upgrade-Insecure-Requests": {},
+ "X-CSRF-Token": {},
+ "X-Requested-With": {},
+ "Strict-Transport-Security": {},
+ "Content-Security-Policy": {},
+ "X-Content-Type-Options": {},
+ "X-Frame-Options": {},
+ "X-XSS-Protection": {},
+ "Public-Key-Pins": {},
+ "Referrer-Policy": {},
+ "Access-Control-Allow-Origin": {},
+ "Access-Control-Allow-Credentials": {},
+ "Access-Control-Expose-Headers": {},
+ "Access-Control-Max-Age": {},
+ "Access-Control-Allow-Methods": {},
+ "Access-Control-Allow-Headers": {},
+ "Server": {},
+ "X-Powered-By": {},
+ "X-AspNet-Version": {},
+ "X-AspNetMvc-Version": {},
+ "ETag": {},
+ "Vary": {},
+ "Expires": {},
+ "Last-Modified": {},
+ "X-Cache": {},
+ "X-Proxy-ID": {},
+ "CF-Ray": {}, // Cloudflare
+ "X-Served-By": {}, // Varnish, etc.
+ "X-Cache-Hits": {},
+ "Content-Encoding": {},
+ "Transfer-Encoding": {},
+ "Location": {},
+ "WWW-Authenticate": {},
+ "Proxy-Authenticate": {},
+ "X-Access-Token": {},
+ "X-Refresh-Token": {},
+ "Link": {},
+ "X-Content-Duration": {},
+ "X-UA-Compatible": {},
+ "X-RateLimit-Limit": {}, // Rate limiting header
+ "X-RateLimit-Remaining": {}, // Rate limiting header
+ "X-RateLimit-Reset": {}, // Rate limiting header
+}
diff --git a/pkg/fuzz/component/headers_test.go b/pkg/fuzz/component/headers_test.go
new file mode 100644
index 0000000000..db5574a372
--- /dev/null
+++ b/pkg/fuzz/component/headers_test.go
@@ -0,0 +1,51 @@
+package component
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+ "github.com/stretchr/testify/require"
+)
+
+func TestHeaderComponent(t *testing.T) {
+ req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req.Header.Set("User-Agent", "test-agent")
+
+ header := NewHeader()
+ _, err = header.Parse(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var keys []string
+ var values []string
+ _ = header.Iterate(func(key string, value interface{}) error {
+ keys = append(keys, key)
+ switch v := value.(type) {
+ case string:
+ values = append(values, v)
+ case []string:
+ values = append(values, v...)
+ }
+ return nil
+ })
+
+ require.Equal(t, []string{"User-Agent"}, keys, "unexpected keys")
+ require.Equal(t, []string{"test-agent"}, values, "unexpected values")
+
+ err = header.SetValue("User-Agent", "new-agent")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rebuilt, err := header.Rebuild()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ require.Equal(t, "new-agent", rebuilt.Header.Get("User-Agent"), "unexpected header value")
+}
diff --git a/pkg/fuzz/component/path.go b/pkg/fuzz/component/path.go
new file mode 100644
index 0000000000..c1d31cee69
--- /dev/null
+++ b/pkg/fuzz/component/path.go
@@ -0,0 +1,83 @@
+package component
+
+import (
+ "context"
+
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat"
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+// Path is a component for a request Path
+type Path struct {
+ value *Value
+
+ req *retryablehttp.Request
+}
+
+var _ Component = &Path{}
+
+// NewPath creates a new URL component
+func NewPath() *Path {
+ return &Path{}
+}
+
+// Name returns the name of the component
+func (q *Path) Name() string {
+ return RequestPathComponent
+}
+
+// Parse parses the component and returns the
+// parsed component
+func (q *Path) Parse(req *retryablehttp.Request) (bool, error) {
+ q.req = req
+ q.value = NewValue(req.URL.Path)
+
+ parsed, err := dataformat.Get(dataformat.RawDataFormat).Decode(q.value.String())
+ if err != nil {
+ return false, err
+ }
+ q.value.SetParsed(parsed, dataformat.RawDataFormat)
+ return true, nil
+}
+
+// Iterate iterates through the component
+func (q *Path) Iterate(callback func(key string, value interface{}) error) error {
+ for key, value := range q.value.Parsed() {
+ if err := callback(key, value); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// SetValue sets a value in the component
+// for a key
+func (q *Path) SetValue(key string, value string) error {
+ if !q.value.SetParsedValue(key, value) {
+ return ErrSetValue
+ }
+ return nil
+}
+
+// Delete deletes a key from the component
+func (q *Path) Delete(key string) error {
+ if !q.value.Delete(key) {
+ return ErrKeyNotFound
+ }
+ return nil
+}
+
+// Rebuild returns a new request with the
+// component rebuilt
+func (q *Path) Rebuild() (*retryablehttp.Request, error) {
+ encoded, err := q.value.Encode()
+ if err != nil {
+ return nil, errors.Wrap(err, "could not encode query")
+ }
+ cloned := q.req.Clone(context.Background())
+ if err := cloned.UpdateRelPath(encoded, true); err != nil {
+ cloned.URL.RawPath = encoded
+ }
+ return cloned, nil
+}
diff --git a/pkg/fuzz/component/path_test.go b/pkg/fuzz/component/path_test.go
new file mode 100644
index 0000000000..859ffcde12
--- /dev/null
+++ b/pkg/fuzz/component/path_test.go
@@ -0,0 +1,46 @@
+package component
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+ "github.com/stretchr/testify/require"
+)
+
+func TestURLComponent(t *testing.T) {
+ req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com/testpath", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ urlComponent := NewPath()
+ _, err = urlComponent.Parse(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var keys []string
+ var values []string
+ _ = urlComponent.Iterate(func(key string, value interface{}) error {
+ keys = append(keys, key)
+ values = append(values, value.(string))
+ return nil
+ })
+
+ require.Equal(t, []string{"value"}, keys, "unexpected keys")
+ require.Equal(t, []string{"/testpath"}, values, "unexpected values")
+
+ err = urlComponent.SetValue("value", "/newpath")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rebuilt, err := urlComponent.Rebuild()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ require.Equal(t, "/newpath", rebuilt.URL.Path, "unexpected URL path")
+ require.Equal(t, "https://example.com/newpath", rebuilt.URL.String(), "unexpected full URL")
+}
diff --git a/pkg/fuzz/component/query.go b/pkg/fuzz/component/query.go
new file mode 100644
index 0000000000..0952c97986
--- /dev/null
+++ b/pkg/fuzz/component/query.go
@@ -0,0 +1,92 @@
+package component
+
+import (
+ "context"
+
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat"
+ "github.com/projectdiscovery/retryablehttp-go"
+ urlutil "github.com/projectdiscovery/utils/url"
+)
+
+// Query is a component for a request query
+type Query struct {
+ value *Value
+
+ req *retryablehttp.Request
+}
+
+var _ Component = &Query{}
+
+// NewQuery creates a new query component
+func NewQuery() *Query {
+ return &Query{}
+}
+
+// Name returns the name of the component
+func (q *Query) Name() string {
+ return RequestQueryComponent
+}
+
+// Parse parses the component and returns the
+// parsed component
+func (q *Query) Parse(req *retryablehttp.Request) (bool, error) {
+ if req.URL.Query().IsEmpty() {
+ return false, nil
+ }
+ q.req = req
+
+ q.value = NewValue(req.URL.Query().Encode())
+
+ parsed, err := dataformat.Get(dataformat.FormDataFormat).Decode(q.value.String())
+ if err != nil {
+ return false, err
+ }
+ q.value.SetParsed(parsed, dataformat.FormDataFormat)
+ return true, nil
+}
+
+// Iterate iterates through the component
+func (q *Query) Iterate(callback func(key string, value interface{}) error) error {
+ for key, value := range q.value.Parsed() {
+ if err := callback(key, value); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// SetValue sets a value in the component
+// for a key
+func (q *Query) SetValue(key string, value string) error {
+ if !q.value.SetParsedValue(key, value) {
+ return ErrSetValue
+ }
+ return nil
+}
+
+// Delete deletes a key from the component
+func (q *Query) Delete(key string) error {
+ if !q.value.Delete(key) {
+ return ErrKeyNotFound
+ }
+ return nil
+}
+
+// Rebuild returns a new request with the
+// component rebuilt
+func (q *Query) Rebuild() (*retryablehttp.Request, error) {
+ encoded, err := q.value.Encode()
+ if err != nil {
+ return nil, errors.Wrap(err, "could not encode query")
+ }
+ cloned := q.req.Clone(context.Background())
+ cloned.URL.RawQuery = encoded
+
+ // Clear the query parameters and re-add them
+ cloned.Params = nil
+ cloned.Params = urlutil.NewOrderedParams()
+ cloned.Params.Decode(encoded)
+ cloned.Update()
+ return cloned, nil
+}
diff --git a/pkg/fuzz/component/query_test.go b/pkg/fuzz/component/query_test.go
new file mode 100644
index 0000000000..48fe5aa26d
--- /dev/null
+++ b/pkg/fuzz/component/query_test.go
@@ -0,0 +1,46 @@
+package component
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+ "github.com/stretchr/testify/require"
+)
+
+func TestQueryComponent(t *testing.T) {
+ req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com?foo=bar", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ query := NewQuery()
+ _, err = query.Parse(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var keys []string
+ var values []string
+ _ = query.Iterate(func(key string, value interface{}) error {
+ keys = append(keys, key)
+ values = append(values, value.(string))
+ return nil
+ })
+
+ require.Equal(t, []string{"foo"}, keys, "unexpected keys")
+ require.Equal(t, []string{"bar"}, values, "unexpected values")
+
+ err = query.SetValue("foo", "baz")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rebuilt, err := query.Rebuild()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ require.Equal(t, "foo=baz", rebuilt.URL.RawQuery, "unexpected query string")
+ require.Equal(t, "https://example.com?foo=baz", rebuilt.URL.String(), "unexpected url")
+}
diff --git a/pkg/fuzz/component/value.go b/pkg/fuzz/component/value.go
new file mode 100644
index 0000000000..c6d7a9603a
--- /dev/null
+++ b/pkg/fuzz/component/value.go
@@ -0,0 +1,120 @@
+package component
+
+import (
+ "strconv"
+
+ "github.com/leslie-qiwa/flat"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat"
+)
+
+// Value is a value component containing a single
+// parameter for the component
+//
+// It is a type of container that is used to represent
+// all the data values that are used in a request.
+type Value struct {
+ data string
+ parsed map[string]interface{}
+ dataFormat string
+}
+
+// NewValue returns a new value component
+func NewValue(data string) *Value {
+ if data == "" {
+ return &Value{}
+ }
+ v := &Value{data: data}
+
+ // Do any dataformat decoding on the data if needed
+ decodedDataformat, err := dataformat.Decode(data)
+ if err == nil && decodedDataformat != nil {
+ v.SetParsed(decodedDataformat.Data, decodedDataformat.DataFormat)
+ }
+ return v
+}
+
+// String returns the string representation of the value
+func (v *Value) String() string {
+ return v.data
+}
+
+// Parsed returns the parsed value
+func (v *Value) Parsed() map[string]interface{} {
+ return v.parsed
+}
+
+// SetParsed sets the parsed value map
+func (v *Value) SetParsed(parsed map[string]interface{}, dataFormat string) {
+ flattened, err := flat.Flatten(parsed, flatOpts)
+ if err == nil {
+ v.parsed = flattened
+ } else {
+ v.parsed = parsed
+ }
+ v.dataFormat = dataFormat
+}
+
+// SetParsedValue sets the parsed value for a key
+// in the parsed map
+func (v *Value) SetParsedValue(key string, value string) bool {
+ origValue, ok := v.parsed[key]
+ if !ok {
+ v.parsed[key] = value
+ return true
+ }
+ // If the value is a list, append to it
+ // otherwise replace it
+ switch v := origValue.(type) {
+ case []interface{}:
+ origValue = append(v, value)
+ case string:
+ origValue = value
+ case int, int32, int64, float32, float64:
+ parsed, err := strconv.ParseInt(value, 10, 64)
+ if err != nil {
+ return false
+ }
+ origValue = parsed
+ case bool:
+ parsed, err := strconv.ParseBool(value)
+ if err != nil {
+ return false
+ }
+ origValue = parsed
+ case nil:
+ origValue = value
+ default:
+ gologger.Error().Msgf("unknown type %T for value %s", v, v)
+ }
+ v.parsed[key] = origValue
+ return true
+}
+
+// Delete removes a key from the parsed value
+func (v *Value) Delete(key string) bool {
+ if _, ok := v.parsed[key]; !ok {
+ return false
+ }
+ delete(v.parsed, key)
+ return true
+}
+
+// Encode encodes the value into a string
+// using the dataformat and encoding
+func (v *Value) Encode() (string, error) {
+ toEncodeStr := v.data
+
+ nested, err := flat.Unflatten(v.parsed, flatOpts)
+ if err != nil {
+ return "", err
+ }
+ if v.dataFormat != "" {
+ dataformatStr, err := dataformat.Encode(nested, v.dataFormat)
+ if err != nil {
+ return "", err
+ }
+ toEncodeStr = dataformatStr
+ }
+ return toEncodeStr, nil
+}
diff --git a/pkg/fuzz/component/value_test.go b/pkg/fuzz/component/value_test.go
new file mode 100644
index 0000000000..bafd02775c
--- /dev/null
+++ b/pkg/fuzz/component/value_test.go
@@ -0,0 +1,39 @@
+package component
+
+import (
+ "testing"
+
+ "github.com/leslie-qiwa/flat"
+ "github.com/stretchr/testify/require"
+)
+
+func TestFlatMap_FlattenUnflatten(t *testing.T) {
+ data := map[string]interface{}{
+ "foo": "bar",
+ "bar": map[string]interface{}{
+ "baz": "foo",
+ },
+ "slice": []interface{}{
+ "foo",
+ "bar",
+ },
+ "with.dot": map[string]interface{}{
+ "foo": "bar",
+ },
+ }
+
+ opts := &flat.Options{
+ Safe: true,
+ Delimiter: "~",
+ }
+ flattened, err := flat.Flatten(data, opts)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ nested, err := flat.Unflatten(flattened, opts)
+ if err != nil {
+ t.Fatal(err)
+ }
+ require.Equal(t, data, nested, "unexpected data")
+}
diff --git a/pkg/fuzz/dataformat/dataformat.go b/pkg/fuzz/dataformat/dataformat.go
new file mode 100644
index 0000000000..a07abc28f4
--- /dev/null
+++ b/pkg/fuzz/dataformat/dataformat.go
@@ -0,0 +1,92 @@
+package dataformat
+
+import (
+ "errors"
+ "fmt"
+)
+
+// dataformats is a list of dataformats
+var dataformats map[string]DataFormat
+
+func init() {
+ dataformats = make(map[string]DataFormat)
+
+ // register the default data formats
+ RegisterDataFormat(NewJSON())
+ RegisterDataFormat(NewXML())
+ RegisterDataFormat(NewRaw())
+ RegisterDataFormat(NewForm())
+ RegisterDataFormat(NewMultiPartForm())
+}
+
+const (
+ // JSONDataFormat is the name of the JSON data format
+ JSONDataFormat = "json"
+ // XMLDataFormat is the name of the XML data format
+ XMLDataFormat = "xml"
+ // RawDataFormat is the name of the Raw data format
+ RawDataFormat = "raw"
+ // FormDataFormat is the name of the Form data format
+ FormDataFormat = "form"
+ // MultiPartFormDataFormat is the name of the MultiPartForm data format
+ MultiPartFormDataFormat = "multipart/form-data"
+)
+
+// Get returns the dataformat by name
+func Get(name string) DataFormat {
+ return dataformats[name]
+}
+
+// RegisterEncoder registers an encoder
+func RegisterDataFormat(dataformat DataFormat) {
+ dataformats[dataformat.Name()] = dataformat
+}
+
+// DataFormat is an interface for encoding and decoding
+type DataFormat interface {
+ // IsType returns true if the data is of the type
+ IsType(data string) bool
+ // Name returns the name of the encoder
+ Name() string
+ // Encode encodes the data into a format
+ Encode(data map[string]interface{}) (string, error)
+ // Decode decodes the data from a format
+ Decode(input string) (map[string]interface{}, error)
+}
+
+// Decoded is a decoded data format
+type Decoded struct {
+ // DataFormat is the data format
+ DataFormat string
+ // Data is the decoded data
+ Data map[string]interface{}
+}
+
+// Decode decodes the data from a format
+func Decode(data string) (*Decoded, error) {
+ for _, dataformat := range dataformats {
+ if dataformat.IsType(data) {
+ decoded, err := dataformat.Decode(data)
+ if err != nil {
+ return nil, err
+ }
+ value := &Decoded{
+ DataFormat: dataformat.Name(),
+ Data: decoded,
+ }
+ return value, nil
+ }
+ }
+ return nil, nil
+}
+
+// Encode encodes the data into a format
+func Encode(data map[string]interface{}, dataformat string) (string, error) {
+ if dataformat == "" {
+ return "", errors.New("dataformat is required")
+ }
+ if encoder, ok := dataformats[dataformat]; ok {
+ return encoder.Encode(data)
+ }
+ return "", fmt.Errorf("dataformat %s is not supported", dataformat)
+}
diff --git a/pkg/fuzz/dataformat/dataformat_test.go b/pkg/fuzz/dataformat/dataformat_test.go
new file mode 100644
index 0000000000..7595f9b962
--- /dev/null
+++ b/pkg/fuzz/dataformat/dataformat_test.go
@@ -0,0 +1,54 @@
+package dataformat
+
+import (
+ "testing"
+)
+
+func TestDataformatDecodeEncode_JSON(t *testing.T) {
+ obj := `{"foo":"bar"}`
+
+ decoded, err := Decode(obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if decoded.DataFormat != "json" {
+ t.Fatal("unexpected data format")
+ }
+ if decoded.Data["foo"] != "bar" {
+ t.Fatal("unexpected data")
+ }
+
+ encoded, err := Encode(decoded.Data, decoded.DataFormat)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if encoded != obj {
+ t.Fatal("unexpected data")
+ }
+}
+
+func TestDataformatDecodeEncode_XML(t *testing.T) {
+ obj := `bar `
+
+ decoded, err := Decode(obj)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if decoded.DataFormat != "xml" {
+ t.Fatal("unexpected data format")
+ }
+ if decoded.Data["foo"].(map[string]interface{})["#text"] != "bar" {
+ t.Fatal("unexpected data")
+ }
+ if decoded.Data["foo"].(map[string]interface{})["-attr"] != "baz" {
+ t.Fatal("unexpected data")
+ }
+
+ encoded, err := Encode(decoded.Data, decoded.DataFormat)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if encoded != obj {
+ t.Fatal("unexpected data")
+ }
+}
diff --git a/pkg/fuzz/dataformat/form.go b/pkg/fuzz/dataformat/form.go
new file mode 100644
index 0000000000..088a62fe11
--- /dev/null
+++ b/pkg/fuzz/dataformat/form.go
@@ -0,0 +1,61 @@
+package dataformat
+
+import (
+ "net/url"
+)
+
+type Form struct{}
+
+var (
+ _ DataFormat = &Form{}
+)
+
+// NewForm returns a new Form encoder
+func NewForm() *Form {
+ return &Form{}
+}
+
+// IsType returns true if the data is Form encoded
+func (f *Form) IsType(data string) bool {
+ return false
+}
+
+// Encode encodes the data into Form format
+func (f *Form) Encode(data map[string]interface{}) (string, error) {
+ query := url.Values{}
+ for key, value := range data {
+ switch v := value.(type) {
+ case []interface{}:
+ for _, val := range v {
+ query.Add(key, val.(string))
+ }
+ case string:
+ query.Set(key, v)
+ }
+ }
+ encoded := query.Encode()
+ return encoded, nil
+}
+
+// Decode decodes the data from Form format
+func (f *Form) Decode(data string) (map[string]interface{}, error) {
+ parsed, err := url.ParseQuery(data)
+ if err != nil {
+ return nil, err
+ }
+
+ values := make(map[string]interface{})
+ for key, value := range parsed {
+ if len(value) == 1 {
+ values[key] = value[0]
+ } else {
+ values[key] = value
+ }
+ }
+ return values, nil
+}
+
+// Name returns the name of the encoder
+func (f *Form) Name() string {
+ return FormDataFormat
+}
diff --git a/pkg/fuzz/dataformat/json.go b/pkg/fuzz/dataformat/json.go
new file mode 100644
index 0000000000..3979ed5e65
--- /dev/null
+++ b/pkg/fuzz/dataformat/json.go
@@ -0,0 +1,48 @@
+package dataformat
+
+import (
+ "strings"
+
+ jsoniter "github.com/json-iterator/go"
+)
+
+// JSON is a JSON encoder
+//
+// For now JSON only supports objects as the root data type
+// and not arrays
+//
+// TODO: Support arrays + other JSON oddities by
+// adding more attirbutes to the map[string]interface{}
+type JSON struct{}
+
+var (
+ _ DataFormat = &JSON{}
+)
+
+// NewJSON returns a new JSON encoder
+func NewJSON() *JSON {
+ return &JSON{}
+}
+
+// IsType returns true if the data is JSON encoded
+func (j *JSON) IsType(data string) bool {
+ return strings.HasPrefix(data, "{") && strings.HasSuffix(data, "}")
+}
+
+// Encode encodes the data into JSON format
+func (j *JSON) Encode(data map[string]interface{}) (string, error) {
+ encoded, err := jsoniter.Marshal(data)
+ return string(encoded), err
+}
+
+// Decode decodes the data from JSON format
+func (j *JSON) Decode(data string) (map[string]interface{}, error) {
+ var decoded map[string]interface{}
+ err := jsoniter.Unmarshal([]byte(data), &decoded)
+ return decoded, err
+}
+
+// Name returns the name of the encoder
+func (j *JSON) Name() string {
+ return JSONDataFormat
+}
diff --git a/pkg/fuzz/dataformat/multipart.go b/pkg/fuzz/dataformat/multipart.go
new file mode 100644
index 0000000000..658f77b321
--- /dev/null
+++ b/pkg/fuzz/dataformat/multipart.go
@@ -0,0 +1,116 @@
+package dataformat
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "mime"
+ "mime/multipart"
+)
+
+type MultiPartForm struct {
+ boundary string
+}
+
+var (
+ _ DataFormat = &MultiPartForm{}
+)
+
+// NewMultiPartForm returns a new MultiPartForm encoder
+func NewMultiPartForm() *MultiPartForm {
+ return &MultiPartForm{}
+}
+
+// IsType returns true if the data is MultiPartForm encoded
+func (m *MultiPartForm) IsType(data string) bool {
+ // This method should be implemented to detect if the data is multipart form encoded
+ return false
+}
+
+// Encode encodes the data into MultiPartForm format
+func (m *MultiPartForm) Encode(data map[string]interface{}) (string, error) {
+ var b bytes.Buffer
+ w := multipart.NewWriter(&b)
+ if err := w.SetBoundary(m.boundary); err != nil {
+ return "", err
+ }
+
+ for key, value := range data {
+ var fw io.Writer
+ var err error
+ // Add field
+ if fw, err = w.CreateFormField(key); err != nil {
+ return "", err
+ }
+ if _, err = fw.Write([]byte(value.(string))); err != nil {
+ return "", err
+ }
+ }
+
+ w.Close()
+ return b.String(), nil
+}
+
+// ParseBoundary parses the boundary from the content type
+func (m *MultiPartForm) ParseBoundary(contentType string) error {
+ _, params, err := mime.ParseMediaType(contentType)
+ if err != nil {
+ return err
+ }
+ m.boundary = params["boundary"]
+ if m.boundary == "" {
+ return fmt.Errorf("no boundary found in the content type")
+ }
+ return nil
+}
+
+// Decode decodes the data from MultiPartForm format
+func (m *MultiPartForm) Decode(data string) (map[string]interface{}, error) {
+ // Create a buffer from the string data
+ b := bytes.NewBufferString(data)
+ // The boundary parameter should be extracted from the Content-Type header of the HTTP request
+ // which is not available in this context, so this is a placeholder for demonstration.
+ // You will need to pass the actual boundary value to this function.
+ r := multipart.NewReader(b, m.boundary)
+
+ form, err := r.ReadForm(32 << 20) // 32MB is the max memory used to parse the form
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ _ = form.RemoveAll()
+ }()
+
+ result := make(map[string]interface{})
+ for key, values := range form.Value {
+ if len(values) > 1 {
+ result[key] = values
+ } else {
+ result[key] = values[0]
+ }
+ }
+ for key, files := range form.File {
+ fileContents := []interface{}{}
+ for _, fileHeader := range files {
+ file, err := fileHeader.Open()
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ buffer := new(bytes.Buffer)
+ if _, err := buffer.ReadFrom(file); err != nil {
+ return nil, err
+ }
+ fileContents = append(fileContents, buffer.String())
+ }
+ result[key] = fileContents
+ }
+
+ return result, nil
+}
+
+// Name returns the name of the encoder
+func (m *MultiPartForm) Name() string {
+ return "multipart/form-data"
+}
diff --git a/pkg/fuzz/dataformat/raw.go b/pkg/fuzz/dataformat/raw.go
new file mode 100644
index 0000000000..70431528fb
--- /dev/null
+++ b/pkg/fuzz/dataformat/raw.go
@@ -0,0 +1,34 @@
+package dataformat
+
+type Raw struct{}
+
+var (
+ _ DataFormat = &Raw{}
+)
+
+// NewRaw returns a new Raw encoder
+func NewRaw() *Raw {
+ return &Raw{}
+}
+
+// IsType returns true if the data is Raw encoded
+func (r *Raw) IsType(data string) bool {
+ return false
+}
+
+// Encode encodes the data into Raw format
+func (r *Raw) Encode(data map[string]interface{}) (string, error) {
+ return data["value"].(string), nil
+}
+
+// Decode decodes the data from Raw format
+func (r *Raw) Decode(data string) (map[string]interface{}, error) {
+ return map[string]interface{}{
+ "value": data,
+ }, nil
+}
+
+// Name returns the name of the encoder
+func (r *Raw) Name() string {
+ return RawDataFormat
+}
diff --git a/pkg/fuzz/dataformat/xml.go b/pkg/fuzz/dataformat/xml.go
new file mode 100644
index 0000000000..0609031ba8
--- /dev/null
+++ b/pkg/fuzz/dataformat/xml.go
@@ -0,0 +1,62 @@
+package dataformat
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/clbanning/mxj/v2"
+)
+
+// XML is an XML encoder
+type XML struct{}
+
+// NewXML returns a new XML encoder
+func NewXML() *XML {
+ return &XML{}
+}
+
+// IsType returns true if the data is XML encoded
+func (x *XML) IsType(data string) bool {
+ return strings.HasPrefix(data, "<") && strings.HasSuffix(data, ">")
+}
+
+// Encode encodes the data into XML format
+func (x *XML) Encode(data map[string]interface{}) (string, error) {
+ var header string
+ if value, ok := data["#_xml_header"]; ok && value != nil {
+ header = value.(string)
+ delete(data, "#_xml_header")
+ }
+ marshalled, err := mxj.Map(data).Xml()
+ if err != nil {
+ return "", err
+ }
+ if header != "" {
+ return fmt.Sprintf("%s?>%s", header, string(marshalled)), nil
+ }
+ return string(marshalled), err
+}
+
+var xmlHeader = regexp.MustCompile(`\<\?(.*)\?\>`)
+
+// Decode decodes the data from XML format
+func (x *XML) Decode(data string) (map[string]interface{}, error) {
+ var prefixStr string
+ prefix := xmlHeader.FindAllStringSubmatch(data, -1)
+ if len(prefix) > 0 {
+ prefixStr = prefix[0][1]
+ }
+
+ decoded, err := mxj.NewMapXml([]byte(data))
+ if err != nil {
+ return nil, err
+ }
+ decoded["#_xml_header"] = prefixStr
+ return decoded, nil
+}
+
+// Name returns the name of the encoder
+func (x *XML) Name() string {
+ return XMLDataFormat
+}
diff --git a/pkg/protocols/common/fuzz/doc.go b/pkg/fuzz/doc.go
similarity index 100%
rename from pkg/protocols/common/fuzz/doc.go
rename to pkg/fuzz/doc.go
diff --git a/pkg/fuzz/execute.go b/pkg/fuzz/execute.go
new file mode 100644
index 0000000000..3a86ef7e33
--- /dev/null
+++ b/pkg/fuzz/execute.go
@@ -0,0 +1,275 @@
+package fuzz
+
+import (
+ "fmt"
+ "io"
+ "regexp"
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/component"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
+ "github.com/projectdiscovery/retryablehttp-go"
+ errorutil "github.com/projectdiscovery/utils/errors"
+ urlutil "github.com/projectdiscovery/utils/url"
+)
+
+var (
+ ErrRuleNotApplicable = errorutil.NewWithFmt("rule not applicable : %v")
+)
+
+// IsErrRuleNotApplicable checks if an error is due to rule not applicable
+func IsErrRuleNotApplicable(err error) bool {
+ if err == nil {
+ return false
+ }
+ if strings.Contains(err.Error(), "rule not applicable") {
+ return true
+ }
+ return false
+}
+
+// ExecuteRuleInput is the input for rule Execute function
+type ExecuteRuleInput struct {
+ // Input is the context args input
+ Input *contextargs.Context
+ // Callback is the callback for generated rule requests
+ Callback func(GeneratedRequest) bool
+ // InteractURLs contains interact urls for execute call
+ InteractURLs []string
+ // Values contains dynamic values for the rule
+ Values map[string]interface{}
+ // BaseRequest is the base http request for fuzzing rule
+ BaseRequest *retryablehttp.Request
+}
+
+// GeneratedRequest is a single generated request for rule
+type GeneratedRequest struct {
+ // Request is the http request for rule
+ Request *retryablehttp.Request
+ // InteractURLs is the list of interactsh urls
+ InteractURLs []string
+ // DynamicValues contains dynamic values map
+ DynamicValues map[string]interface{}
+ // Component is the component for the request
+ Component component.Component
+}
+
+// Execute executes a fuzzing rule accepting a callback on which
+// generated requests are returned.
+//
+// Input is not thread safe and should not be shared between concurrent
+// goroutines.
+func (rule *Rule) Execute(input *ExecuteRuleInput) (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("got panic while executing rule: %v", r)
+ }
+ }()
+ if !rule.isInputURLValid(input.Input) {
+ return ErrRuleNotApplicable.Msgf("invalid input url: %v", input.Input.MetaInput.Input)
+ }
+ if input.BaseRequest == nil && input.Input.MetaInput.ReqResp == nil {
+ return ErrRuleNotApplicable.Msgf("both base request and reqresp are nil for %v", input.Input.MetaInput.Input)
+ }
+
+ var finalComponentList []component.Component
+ // match rule part with component name
+ for _, componentName := range component.Components {
+ if rule.partType != requestPartType && rule.Part != componentName {
+ continue
+ }
+ component := component.New(componentName)
+ discovered, err := component.Parse(input.BaseRequest)
+ if err != nil {
+ gologger.Verbose().Msgf("Could not parse component %s: %s\n", componentName, err)
+ continue
+ }
+ if !discovered {
+ continue
+ }
+ // check rule applicable on this component
+ if !rule.checkRuleApplicableOnComponent(component) {
+ continue
+ }
+ finalComponentList = append(finalComponentList, component)
+ }
+
+ if len(finalComponentList) == 0 {
+ return ErrRuleNotApplicable.Msgf("no component matched on this rule")
+ }
+
+ baseValues := input.Values
+ if rule.generator == nil {
+ for _, component := range finalComponentList {
+ evaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(baseValues, rule.options.Interactsh)
+ input.Values = generators.MergeMaps(evaluatedValues, baseValues, rule.options.Constants)
+ input.InteractURLs = interactURLs
+ err := rule.executeRuleValues(input, component)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+mainLoop:
+ for _, component := range finalComponentList {
+ iterator := rule.generator.NewIterator()
+ for {
+ values, next := iterator.Value()
+ if !next {
+ continue mainLoop
+ }
+ evaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(generators.MergeMaps(values, baseValues), rule.options.Interactsh)
+ input.InteractURLs = interactURLs
+ input.Values = generators.MergeMaps(values, evaluatedValues, baseValues, rule.options.Constants)
+
+ if err := rule.executeRuleValues(input, component); err != nil {
+ if err == io.EOF {
+ return nil
+ }
+ gologger.Warning().Msgf("[%s] Could not execute rule: %s\n", rule.options.TemplateID, err)
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// isInputURLValid returns true if url is valid after parsing it
+func (rule *Rule) isInputURLValid(input *contextargs.Context) bool {
+ if input == nil || input.MetaInput == nil || input.MetaInput.Input == "" {
+ return false
+ }
+ _, err := urlutil.Parse(input.MetaInput.Input)
+ return err == nil
+}
+
+// executeRuleValues executes a rule with a set of values
+func (rule *Rule) executeRuleValues(input *ExecuteRuleInput, ruleComponent component.Component) error {
+ // if we are only fuzzing values
+ if len(rule.Fuzz.Value) > 0 {
+ for _, value := range rule.Fuzz.Value {
+ if err := rule.executePartRule(input, ValueOrKeyValue{Value: value}, ruleComponent); err != nil {
+ if component.IsErrSetValue(err) {
+ // this are errors due to format restrictions
+ // ex: fuzzing string value in a json int field
+ continue
+ }
+ return err
+ }
+ }
+ return nil
+ }
+
+ // if we are fuzzing both keys and values
+ if rule.Fuzz.KV != nil {
+ var gotErr error
+ rule.Fuzz.KV.Iterate(func(key, value string) bool {
+ if err := rule.executePartRule(input, ValueOrKeyValue{Key: key, Value: value}, ruleComponent); err != nil {
+ if component.IsErrSetValue(err) {
+ // this are errors due to format restrictions
+ // ex: fuzzing string value in a json int field
+ return true
+ }
+ gotErr = err
+ return false
+ }
+ return true
+ })
+ // if mode is multiple now build and execute it
+ if rule.modeType == multipleModeType {
+ req, err := ruleComponent.Rebuild()
+ if err != nil {
+ return err
+ }
+ if gotErr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent); gotErr != nil {
+ return gotErr
+ }
+ }
+ return gotErr
+ }
+
+ // something else is wrong
+ return fmt.Errorf("no fuzz values specified")
+}
+
+// Compile compiles a fuzzing rule and initializes it for operation
+func (rule *Rule) Compile(generator *generators.PayloadGenerator, options *protocols.ExecutorOptions) error {
+ // If a payload generator is specified from base request, use it
+ // for payload values.
+ if generator != nil {
+ rule.generator = generator
+ }
+ rule.options = options
+
+ // Resolve the default enums
+ if rule.Mode != "" {
+ if valueType, ok := stringToModeType[rule.Mode]; !ok {
+ return errors.Errorf("invalid mode value specified: %s", rule.Mode)
+ } else {
+ rule.modeType = valueType
+ }
+ } else {
+ rule.modeType = multipleModeType
+ }
+ if rule.Part != "" {
+ if valueType, ok := stringToPartType[rule.Part]; !ok {
+ return errors.Errorf("invalid part value specified: %s", rule.Part)
+ } else {
+ rule.partType = valueType
+ }
+ } else {
+ rule.partType = queryPartType
+ }
+
+ if rule.Type != "" {
+ if valueType, ok := stringToRuleType[rule.Type]; !ok {
+ return errors.Errorf("invalid type value specified: %s", rule.Type)
+ } else {
+ rule.ruleType = valueType
+ }
+ } else {
+ rule.ruleType = replaceRuleType
+ }
+
+ // Initialize other required regexes and maps
+ if len(rule.Keys) > 0 {
+ rule.keysMap = make(map[string]struct{})
+ }
+ for _, key := range rule.Keys {
+ rule.keysMap[strings.ToLower(key)] = struct{}{}
+ }
+ for _, value := range rule.ValuesRegex {
+ compiled, err := regexp.Compile(value)
+ if err != nil {
+ return errors.Wrap(err, "could not compile value regex")
+ }
+ rule.valuesRegex = append(rule.valuesRegex, compiled)
+ }
+ for _, value := range rule.KeysRegex {
+ compiled, err := regexp.Compile(value)
+ if err != nil {
+ return errors.Wrap(err, "could not compile key regex")
+ }
+ rule.keysRegex = append(rule.keysRegex, compiled)
+ }
+ if rule.ruleType != replaceRegexRuleType {
+ if rule.ReplaceRegex != "" {
+ return errors.Errorf("replace-regex is only applicable for replace and replace-regex rule types")
+ }
+ } else {
+ if rule.ReplaceRegex == "" {
+ return errors.Errorf("replace-regex is required for replace-regex rule type")
+ }
+ compiled, err := regexp.Compile(rule.ReplaceRegex)
+ if err != nil {
+ return errors.Wrap(err, "could not compile replace regex")
+ }
+ rule.replaceRegex = compiled
+ }
+ return nil
+}
diff --git a/pkg/protocols/common/fuzz/fuzz.go b/pkg/fuzz/fuzz.go
similarity index 75%
rename from pkg/protocols/common/fuzz/fuzz.go
rename to pkg/fuzz/fuzz.go
index 0cf37a23a4..c70bc3b2cc 100644
--- a/pkg/protocols/common/fuzz/fuzz.go
+++ b/pkg/fuzz/fuzz.go
@@ -20,7 +20,7 @@ type Rule struct {
// - "prefix"
// - "postfix"
// - "infix"
- Type string `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"title=type of rule,description=Type of fuzzing rule to perform,enum=replace,enum=prefix,enum=postfix,enum=infix"`
+ Type string `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"title=type of rule,description=Type of fuzzing rule to perform,enum=replace,enum=prefix,enum=postfix,enum=infix,enum=replace-regex"`
ruleType ruleType
// description: |
// Part is the part of request to fuzz.
@@ -28,7 +28,7 @@ type Rule struct {
// query fuzzes the query part of url. More parts will be added later.
// values:
// - "query"
- Part string `yaml:"part,omitempty" json:"part,omitempty" jsonschema:"title=part of rule,description=Part of request rule to fuzz,enum=query"`
+ Part string `yaml:"part,omitempty" json:"part,omitempty" jsonschema:"title=part of rule,description=Part of request rule to fuzz,enum=query,enum=header,enum=path,enum=body,enum=cookie,enum=request"`
partType partType
// description: |
// Mode is the mode of fuzzing to perform.
@@ -71,10 +71,20 @@ type Rule struct {
// - name: Examples of fuzz
// value: >
// []string{"{{ssrf}}", "{{interactsh-url}}", "example-value"}
- Fuzz []string `yaml:"fuzz,omitempty" json:"fuzz,omitempty" jsonschema:"title=payloads of fuzz rule,description=Payloads to perform fuzzing substitutions with"`
-
- options *protocols.ExecutorOptions
- generator *generators.PayloadGenerator
+ // or
+ // x-header: 1
+ // x-header: 2
+ Fuzz SliceOrMapSlice `yaml:"fuzz,omitempty" json:"fuzz,omitempty" jsonschema:"title=payloads of fuzz rule,description=Payloads to perform fuzzing substitutions with"`
+ // description: |
+ // replace-regex is regex for regex-replace rule type
+ // it is only required for replace-regex rule type
+ // examples:
+ // - type: replace-regex
+ // replace-regex: "https?://.*"
+ ReplaceRegex string `yaml:"replace-regex,omitempty" json:"replace-regex,omitempty" jsonschema:"title=replace regex of rule,description=Regex for regex-replace rule type"`
+ replaceRegex *regexp.Regexp `yaml:"-" json:"-"`
+ options *protocols.ExecutorOptions
+ generator *generators.PayloadGenerator
}
// ruleType is the type of rule enum declaration
@@ -85,13 +95,15 @@ const (
prefixRuleType
postfixRuleType
infixRuleType
+ replaceRegexRuleType
)
var stringToRuleType = map[string]ruleType{
- "replace": replaceRuleType,
- "prefix": prefixRuleType,
- "postfix": postfixRuleType,
- "infix": infixRuleType,
+ "replace": replaceRuleType,
+ "prefix": prefixRuleType,
+ "postfix": postfixRuleType,
+ "infix": infixRuleType,
+ "replace-regex": replaceRegexRuleType,
}
// partType is the part of rule enum declaration
@@ -100,11 +112,19 @@ type partType int
const (
queryPartType partType = iota + 1
headersPartType
+ pathPartType
+ bodyPartType
+ cookiePartType
+ requestPartType
)
var stringToPartType = map[string]partType{
"query": queryPartType,
- "headers": headersPartType,
+ "header": headersPartType,
+ "path": pathPartType,
+ "body": bodyPartType,
+ "cookie": cookiePartType,
+ "request": requestPartType, // request means all request parts
}
// modeType is the mode of rule enum declaration
diff --git a/pkg/protocols/common/fuzz/fuzz_test.go b/pkg/fuzz/fuzz_test.go
similarity index 100%
rename from pkg/protocols/common/fuzz/fuzz_test.go
rename to pkg/fuzz/fuzz_test.go
diff --git a/pkg/fuzz/parts.go b/pkg/fuzz/parts.go
new file mode 100644
index 0000000000..9b24446bb7
--- /dev/null
+++ b/pkg/fuzz/parts.go
@@ -0,0 +1,210 @@
+package fuzz
+
+import (
+ "io"
+ "strings"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/component"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
+ "github.com/projectdiscovery/nuclei/v3/pkg/types"
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+// executePartRule executes part rules based on type
+func (rule *Rule) executePartRule(input *ExecuteRuleInput, payload ValueOrKeyValue, component component.Component) error {
+ return rule.executePartComponent(input, payload, component)
+}
+
+// checkRuleApplicableOnComponent checks if a rule is applicable on given component
+func (rule *Rule) checkRuleApplicableOnComponent(component component.Component) bool {
+ if rule.Part != component.Name() {
+ return false
+ }
+ foundAny := false
+ _ = component.Iterate(func(key string, value interface{}) error {
+ if rule.matchKeyOrValue(key, types.ToString(value)) {
+ foundAny = true
+ return io.EOF
+ }
+ return nil
+ })
+ return foundAny
+}
+
+// executePartComponent executes this rule on a given component and payload
+func (rule *Rule) executePartComponent(input *ExecuteRuleInput, payload ValueOrKeyValue, ruleComponent component.Component) error {
+ if payload.IsKV() {
+ // for kv fuzzing
+ return rule.executePartComponentOnKV(input, payload, ruleComponent)
+ } else {
+ // for value only fuzzing
+ return rule.executePartComponentOnValues(input, payload.Value, ruleComponent)
+ }
+}
+
+// executePartComponentOnValues executes this rule on a given component and payload
+// this supports both single and multiple [ruleType] modes
+// i.e if component has multiple values, they can be replaced once or all depending on mode
+func (rule *Rule) executePartComponentOnValues(input *ExecuteRuleInput, payloadStr string, ruleComponent component.Component) error {
+ finalErr := ruleComponent.Iterate(func(key string, value interface{}) error {
+ valueStr := types.ToString(value)
+ if !rule.matchKeyOrValue(key, valueStr) {
+ // ignore non-matching keys
+ return nil
+ }
+
+ var evaluated string
+ evaluated, input.InteractURLs = rule.executeEvaluate(input, key, valueStr, payloadStr, input.InteractURLs)
+ if err := ruleComponent.SetValue(key, evaluated); err != nil {
+ // gologger.Warning().Msgf("could not set value due to format restriction original(%s, %s[%T]) , new(%s,%s[%T])", key, valueStr, value, key, evaluated, evaluated)
+ return nil
+ }
+
+ if rule.modeType == singleModeType {
+ req, err := ruleComponent.Rebuild()
+ if err != nil {
+ return err
+ }
+
+ if qerr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent); qerr != nil {
+ return qerr
+ }
+ // fmt.Printf("executed with value: %s\n", evaluated)
+ err = ruleComponent.SetValue(key, valueStr) // change back to previous value for temp
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+ if finalErr != nil {
+ return finalErr
+ }
+
+ // We do not support analyzers with
+ // multiple payload mode.
+ if rule.modeType == multipleModeType {
+ req, err := ruleComponent.Rebuild()
+ if err != nil {
+ return err
+ }
+ if qerr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent); qerr != nil {
+ err = qerr
+ return err
+ }
+ }
+ return nil
+}
+
+// executePartComponentOnKV executes this rule on a given component and payload
+// currently only supports single mode
+func (rule *Rule) executePartComponentOnKV(input *ExecuteRuleInput, payload ValueOrKeyValue, ruleComponent component.Component) error {
+ var origKey string
+ var origValue interface{}
+ // when we have a key-value pair, iterate over only 1 value of the component
+ // multiple values (aka multiple mode) not supported for this yet
+ _ = ruleComponent.Iterate(func(key string, value interface{}) error {
+ if key == payload.Key {
+ origKey = key
+ origValue = value
+ }
+ return nil
+ })
+ // iterate over given kv instead of component ones
+ return func(key, value string) error {
+ var evaluated string
+ evaluated, input.InteractURLs = rule.executeEvaluate(input, key, "", value, input.InteractURLs)
+ if err := ruleComponent.SetValue(key, evaluated); err != nil {
+ return err
+ }
+ if rule.modeType == singleModeType {
+ req, err := ruleComponent.Rebuild()
+ if err != nil {
+ return err
+ }
+
+ if qerr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent); qerr != nil {
+ return err
+ }
+
+ // after building change back to original value to avoid repeating it in furthur requests
+ if origKey != "" {
+ err = ruleComponent.SetValue(origKey, types.ToString(origValue)) // change back to previous value for temp
+ if err != nil {
+ return err
+ }
+ } else {
+ _ = ruleComponent.Delete(key) // change back to previous value for temp
+ }
+ }
+ return nil
+ }(payload.Key, payload.Value)
+}
+
+// execWithInput executes a rule with input via callback
+func (rule *Rule) execWithInput(input *ExecuteRuleInput, httpReq *retryablehttp.Request, interactURLs []string, component component.Component) error {
+ request := GeneratedRequest{
+ Request: httpReq,
+ InteractURLs: interactURLs,
+ DynamicValues: input.Values,
+ Component: component,
+ }
+ if !input.Callback(request) {
+ return types.ErrNoMoreRequests
+ }
+ return nil
+}
+
+// executeEvaluate executes evaluation of payload on a key and value and
+// returns completed values to be replaced and processed
+// for fuzzing.
+func (rule *Rule) executeEvaluate(input *ExecuteRuleInput, _, value, payload string, interactshURLs []string) (string, []string) {
+ // TODO: Handle errors
+ values := generators.MergeMaps(input.Values, map[string]interface{}{
+ "value": value,
+ }, rule.options.Options.Vars.AsMap(), rule.options.Variables.GetAll())
+ firstpass, _ := expressions.Evaluate(payload, values)
+ interactData, interactshURLs := rule.options.Interactsh.Replace(firstpass, interactshURLs)
+ evaluated, _ := expressions.Evaluate(interactData, values)
+ replaced := rule.executeRuleTypes(input, value, evaluated)
+ return replaced, interactshURLs
+}
+
+// executeRuleTypes executes replacement for a key and value
+// ex: prefix, postfix, infix, replace , replace-regex
+func (rule *Rule) executeRuleTypes(_ *ExecuteRuleInput, value, replacement string) string {
+ var builder strings.Builder
+ if rule.ruleType == prefixRuleType || rule.ruleType == postfixRuleType {
+ builder.Grow(len(value) + len(replacement))
+ }
+ var returnValue string
+
+ switch rule.ruleType {
+ case prefixRuleType:
+ builder.WriteString(replacement)
+ builder.WriteString(value)
+ returnValue = builder.String()
+ case postfixRuleType:
+ builder.WriteString(value)
+ builder.WriteString(replacement)
+ returnValue = builder.String()
+ case infixRuleType:
+ if len(value) <= 1 {
+ builder.WriteString(value)
+ builder.WriteString(replacement)
+ returnValue = builder.String()
+ } else {
+ middleIndex := len(value) / 2
+ builder.WriteString(value[:middleIndex])
+ builder.WriteString(replacement)
+ builder.WriteString(value[middleIndex:])
+ returnValue = builder.String()
+ }
+ case replaceRuleType:
+ returnValue = replacement
+ case replaceRegexRuleType:
+ returnValue = rule.replaceRegex.ReplaceAllString(value, replacement)
+ }
+ return returnValue
+}
diff --git a/pkg/fuzz/parts_test.go b/pkg/fuzz/parts_test.go
new file mode 100644
index 0000000000..d31f0ea046
--- /dev/null
+++ b/pkg/fuzz/parts_test.go
@@ -0,0 +1,2 @@
+// TODO: Write tests
+package fuzz
diff --git a/pkg/fuzz/type.go b/pkg/fuzz/type.go
new file mode 100644
index 0000000000..6110b2cdfd
--- /dev/null
+++ b/pkg/fuzz/type.go
@@ -0,0 +1,80 @@
+package fuzz
+
+import (
+ "encoding/json"
+ "fmt"
+
+ mapsutil "github.com/projectdiscovery/utils/maps"
+ "gopkg.in/yaml.v2"
+)
+
+var (
+ _ json.Marshaler = &SliceOrMapSlice{}
+ _ json.Unmarshaler = &SliceOrMapSlice{}
+ _ yaml.Marshaler = &SliceOrMapSlice{}
+ _ yaml.Unmarshaler = &SliceOrMapSlice{}
+)
+
+type ValueOrKeyValue struct {
+ Key string
+ Value string
+}
+
+func (v *ValueOrKeyValue) IsKV() bool {
+ return v.Key != ""
+}
+
+type SliceOrMapSlice struct {
+ Value []string
+ KV *mapsutil.OrderedMap[string, string]
+}
+
+// UnmarshalJSON implements json.Unmarshaler interface.
+func (v *SliceOrMapSlice) UnmarshalJSON(data []byte) error {
+ // try to unmashal as a string and fallback to map
+ if err := json.Unmarshal(data, &v.Value); err == nil {
+ return nil
+ }
+ err := json.Unmarshal(data, &v.KV)
+ if err != nil {
+ return fmt.Errorf("object can be a key:value or a string")
+ }
+ return nil
+}
+
+// MarshalJSON implements json.Marshaler interface.
+func (v SliceOrMapSlice) MarshalJSON() ([]byte, error) {
+ if v.KV != nil {
+ return json.Marshal(v.KV)
+ }
+ return json.Marshal(v.Value)
+}
+
+// UnmarshalYAML implements yaml.Unmarshaler interface.
+func (v *SliceOrMapSlice) UnmarshalYAML(callback func(interface{}) error) error {
+ // try to unmarshal it as a string and fallback to map
+ if err := callback(&v.Value); err == nil {
+ return nil
+ }
+
+ // try with a mapslice
+ var node yaml.MapSlice
+ if err := callback(&node); err == nil {
+ tmpx := mapsutil.NewOrderedMap[string, string]()
+ // preserve order
+ for _, v := range node {
+ tmpx.Set(v.Key.(string), v.Value.(string))
+ }
+ v.KV = &tmpx
+ return nil
+ }
+ return fmt.Errorf("object can be a key:value or a string")
+}
+
+// MarshalYAML implements yaml.Marshaler interface.
+func (v SliceOrMapSlice) MarshalYAML() (any, error) {
+ if v.KV != nil {
+ return v.KV, nil
+ }
+ return v.Value, nil
+}
diff --git a/pkg/input/README.md b/pkg/input/README.md
new file mode 100644
index 0000000000..4e8e593283
--- /dev/null
+++ b/pkg/input/README.md
@@ -0,0 +1,29 @@
+## input
+
+input package contains and provides loading, parsing , validating and normalizing of input data
+
+
+## [transform](./transform.go)
+
+Transform package transforms or normalizes the input data before it is sent to protocol executer this step mainly involves changes like adding default ports (if missing) , validating if input is file or directory or url and adjusting the input accordingly etc.
+
+
+## Provider
+
+Provider package contains the interface that every input format should implement for providing that input format to nuclei.
+
+Currently Nuclei Supports three input providers:
+
+1. SimpleInputProvider = A No-Op provider that takes a list of urls and implements the provider interface.
+
+2. HttpInputProvider = A provider that supports loading and parsing input formats that contain complete Http Data like Entire Request, Response etc. Supported formats include Burp,openapi,swagger,postman,proxify etc.
+
+3. ListInputProvider = Legacy/Default Provider that handles all list type inputs like urls,domains,ips,cidrs,files etc.
+
+
+```go
+func NewInputProvider(opts InputOptions) (InputProvider, error)
+```
+
+This function returns a InputProvider based by appropriately selecting input provider based on the input format (i.e either list or http) and returns the provider that can handle that input format.
+
diff --git a/pkg/input/formats/README.md b/pkg/input/formats/README.md
new file mode 100644
index 0000000000..3db9cdc15e
--- /dev/null
+++ b/pkg/input/formats/README.md
@@ -0,0 +1,90 @@
+# formats
+
+Formats implements support for passing a number of request source as input providers to nuclei to be tested for fuzzing related issues.
+
+Currently the following formats are implemented -
+
+- Burp Suite XML Request/Response file
+- Proxify JSONL output file
+- OpenAPI Specification file
+- Postman Collection file
+- Swagger Specification file
+
+Each implementation implements either the entire or a subset of the features of the specifications. These can be increased further to add support as new things or requirements are identified.
+
+Refer to the specific code for each implementation to understand supported features of the specs.
+
+
+## OpenAPI Specification File
+
+It is designed to generate HTTP requests based on an OpenAPI 3.0 Schema. Here is how these schema components are processed:
+
+### Servers
+
+The module supports multiple server URLs defined in the `Servers` section of the OpenAPI document. It will send requests to all the server URLs defined in the schema.
+
+### Paths and Operations
+
+The module supports all HTTP methods defined under each path in the `Paths` section. For each operation on a path, HTTP requests are generated and sent to the defined server URL. If the operation cannot generate a valid request, a warning will be logged.
+
+### Parameters
+
+The module recognizes parameters defined in the `query`, `header`, `path`, and `cookie` categories. When generating requests, if the `requiredOnly` flag is true, only the required parameters are included. Otherwise, all parameters, regardless of their required status, are used.
+
+The `generateExampleFromSchema` function is used to generate suitable example data for each parameter from their respective schema definitions.
+
+### RequestBody
+
+The module also comprehends request bodies and supports various media types defined in the `Content` field. Currently, the following content-types are supported:
+
+- `application/json`: The module creates application-specific JSON from the defined example schema.
+
+- `application/xml`: The example schema is converted into xml format using `mxj` library.
+
+- `application/x-www-form-urlencoded`: The example schema is converted into URL-encoded form data.
+
+- `multipart/form-data`: The module supports multipart form-data and differentiates between fields and files using the `binary` format under the property schema.
+
+- `text/plain`: Converts the example schema into string format and send as plain text.
+
+For unsupported media types, no appropriate content type is found for the body. After setting up the body of the request, the module dumps the request and sends it to the defined server URL.
+
+### Example Request Generation
+
+This module converts each operation into one or more example HTTP requests. Each request is dumped into a string format, accompanied by its method, URL, headers, and body. These are send as a callback for further processing.
+
+_Please note: This document does not cover other features of OpenAPI specification like responses, security schemes, links, callbacks, etc. as these are not currently handled by the module._
+
+## Postman Collection file
+
+This module parser Postman Collection JSON files.
+
+### 1. Request Parsing:
+ Able to parse requests detailed in the Postman package. The parser is capable of interpreting the HTTP method, URL, and Body of each request present in the collection.
+
+### 2. Header Parsing:
+ All HTTP headers set in the collection's request are parsed and set in the request.
+
+### 3. Auth Type Parsing:
+ Able to parse and set the `Authentication` options provided in the postman collection in the request headers.
+ Supported types of authentiction:
+
+ 1. **API Key**: In header
+ 2. **Basic**: Setting basic auth through username, password.
+ 3. **Bearer Token**: Involves setting bearer auth using tokens.
+ 4. **No Auth**: No authentication is set.
+
+Note: Not all parts of the Postman Collection specification are supported. This parser does not currently support Postman variables or collection level variables and items. It also does not support more authentication types than detailed above.
+
+### Limitations:
+* Does not support Postman variables
+* Does not support Collection level variables and items
+* Limited Authentication types supported
+
+## Swagger Specification file
+
+Swagger specification file is converted from OpenAPI 2.0 format to OpenAPI 3.0 format. After this, the OpenAPI parser from above is used.
+
+## Burp XML / Proxify JSONL
+
+These modules are generic and parse raw requests from these respective tools.
\ No newline at end of file
diff --git a/pkg/input/formats/burp/burp.go b/pkg/input/formats/burp/burp.go
new file mode 100644
index 0000000000..6ad5f548b5
--- /dev/null
+++ b/pkg/input/formats/burp/burp.go
@@ -0,0 +1,67 @@
+package burp
+
+import (
+ "encoding/base64"
+ "os"
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/projectdiscovery/utils/conversion"
+ "github.com/seh-msft/burpxml"
+)
+
+// BurpFormat is a Burp XML File parser
+type BurpFormat struct {
+ opts formats.InputFormatOptions
+}
+
+// New creates a new Burp XML File parser
+func New() *BurpFormat {
+ return &BurpFormat{}
+}
+
+var _ formats.Format = &BurpFormat{}
+
+// Name returns the name of the format
+func (j *BurpFormat) Name() string {
+ return "burp"
+}
+
+func (j *BurpFormat) SetOptions(options formats.InputFormatOptions) {
+ j.opts = options
+}
+
+// Parse parses the input and calls the provided callback
+// function for each RawRequest it discovers.
+func (j *BurpFormat) Parse(input string, resultsCb formats.ParseReqRespCallback) error {
+ file, err := os.Open(input)
+ if err != nil {
+ return errors.Wrap(err, "could not open data file")
+ }
+ defer file.Close()
+
+ items, err := burpxml.Parse(file, true)
+ if err != nil {
+ return errors.Wrap(err, "could not decode burp xml schema")
+ }
+
+ // Print the parsed data for verification
+ for _, item := range items.Items {
+ item := item
+ binx, err := base64.StdEncoding.DecodeString(item.Request.Raw)
+ if err != nil {
+ return errors.Wrap(err, "could not decode base64")
+ }
+ if strings.TrimSpace(conversion.String(binx)) == "" {
+ continue
+ }
+ rawRequest, err := types.ParseRawRequestWithURL(conversion.String(binx), item.Url)
+ if err != nil {
+ return errors.Wrap(err, "could not parse raw request")
+ }
+ resultsCb(rawRequest) // TODO: Handle false and true from callback
+ }
+ return nil
+}
diff --git a/pkg/input/formats/burp/burp_test.go b/pkg/input/formats/burp/burp_test.go
new file mode 100644
index 0000000000..330218a9e5
--- /dev/null
+++ b/pkg/input/formats/burp/burp_test.go
@@ -0,0 +1,33 @@
+package burp
+
+import (
+ "testing"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestBurpParse(t *testing.T) {
+ format := New()
+
+ proxifyInputFile := "../testdata/burp.xml"
+
+ var gotMethodsToURLs []string
+
+ err := format.Parse(proxifyInputFile, func(request *types.RequestResponse) bool {
+ gotMethodsToURLs = append(gotMethodsToURLs, request.URL.String())
+ return false
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(gotMethodsToURLs) != 2 {
+ t.Fatalf("invalid number of methods: %d", len(gotMethodsToURLs))
+ }
+ var expectedURLs = []string{
+ "http://localhost:8087/scans",
+ "http://google.com/",
+ }
+ require.ElementsMatch(t, expectedURLs, gotMethodsToURLs, "could not get burp urls")
+}
diff --git a/pkg/input/formats/formats.go b/pkg/input/formats/formats.go
new file mode 100644
index 0000000000..af2b4569c6
--- /dev/null
+++ b/pkg/input/formats/formats.go
@@ -0,0 +1,103 @@
+package formats
+
+import (
+ "errors"
+ "os"
+ "strings"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ fileutil "github.com/projectdiscovery/utils/file"
+ "gopkg.in/yaml.v3"
+)
+
+// ParseReqRespCallback is a callback function for discovered raw requests
+type ParseReqRespCallback func(rr *types.RequestResponse) bool
+
+// InputFormatOptions contains options for the input
+// this can be variables that can be passed or
+// overrides or some other options
+type InputFormatOptions struct {
+ // Variables is list of variables that can be used
+ // while generating requests in given format
+ Variables map[string]interface{}
+ // SkipFormatValidation is used to skip format validation
+ // while debugging or testing if format is invalid then
+ // requests are skipped instead of creating invalid requests
+ SkipFormatValidation bool
+ // RequiredOnly only uses required fields when generating requests
+ // instead of all fields
+ RequiredOnly bool
+}
+
+// Format is an interface implemented by all input formats
+type Format interface {
+ // Name returns the name of the format
+ Name() string
+ // Parse parses the input and calls the provided callback
+ // function for each RawRequest it discovers.
+ Parse(input string, resultsCb ParseReqRespCallback) error
+ // SetOptions sets the options for the input format
+ SetOptions(options InputFormatOptions)
+}
+
+var (
+ DefaultVarDumpFileName = "required_openapi_params.yaml"
+ ErrNoVarsDumpFile = errors.New("no required params file found")
+)
+
+// == OpenAPIParamsCfgFile ==
+// this file is meant to be used in CLI mode
+// to be more interactive and user-friendly when
+// running nuclei with openapi format
+
+// OpenAPIParamsCfgFile is the structure of the required vars dump file
+type OpenAPIParamsCfgFile struct {
+ Var []string `yaml:"var"`
+ OptionalVars []string `yaml:"-"` // this will be written to the file as comments
+}
+
+// ReadOpenAPIVarDumpFile reads the required vars dump file
+func ReadOpenAPIVarDumpFile() (*OpenAPIParamsCfgFile, error) {
+ var vars OpenAPIParamsCfgFile
+ if !fileutil.FileExists(DefaultVarDumpFileName) {
+ return nil, ErrNoVarsDumpFile
+ }
+ bin, err := os.ReadFile(DefaultVarDumpFileName)
+ if err != nil {
+ return nil, err
+ }
+ err = yaml.Unmarshal(bin, &vars)
+ if err != nil {
+ return nil, err
+ }
+ filtered := []string{}
+ for _, v := range vars.Var {
+ v = strings.TrimSpace(v)
+ if !strings.HasSuffix(v, "=") {
+ filtered = append(filtered, v)
+ }
+ }
+ vars.Var = filtered
+ return &vars, nil
+}
+
+// WriteOpenAPIVarDumpFile writes the required vars dump file
+func WriteOpenAPIVarDumpFile(vars *OpenAPIParamsCfgFile) error {
+ f, err := os.OpenFile(DefaultVarDumpFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ bin, err := yaml.Marshal(vars)
+ if err != nil {
+ return err
+ }
+ _, _ = f.Write(bin)
+ if len(vars.OptionalVars) > 0 {
+ _, _ = f.WriteString("\n # Optional parameters\n")
+ for _, v := range vars.OptionalVars {
+ _, _ = f.WriteString(" # - " + v + "=\n")
+ }
+ }
+ return f.Sync()
+}
diff --git a/pkg/input/formats/json/json.go b/pkg/input/formats/json/json.go
new file mode 100644
index 0000000000..fecb1c6b99
--- /dev/null
+++ b/pkg/input/formats/json/json.go
@@ -0,0 +1,74 @@
+package json
+
+import (
+ "encoding/json"
+ "io"
+ "os"
+
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+)
+
+// JSONFormat is a JSON format parser for nuclei
+// input HTTP requests
+type JSONFormat struct {
+ opts formats.InputFormatOptions
+}
+
+// New creates a new JSON format parser
+func New() *JSONFormat {
+ return &JSONFormat{}
+}
+
+var _ formats.Format = &JSONFormat{}
+
+// proxifyRequest is a request for proxify
+type proxifyRequest struct {
+ URL string `json:"url"`
+ Request struct {
+ Header map[string]string `json:"header"`
+ Body string `json:"body"`
+ Raw string `json:"raw"`
+ } `json:"request"`
+}
+
+// Name returns the name of the format
+func (j *JSONFormat) Name() string {
+ return "jsonl"
+}
+
+func (j *JSONFormat) SetOptions(options formats.InputFormatOptions) {
+ j.opts = options
+}
+
+// Parse parses the input and calls the provided callback
+// function for each RawRequest it discovers.
+func (j *JSONFormat) Parse(input string, resultsCb formats.ParseReqRespCallback) error {
+ file, err := os.Open(input)
+ if err != nil {
+ return errors.Wrap(err, "could not open json file")
+ }
+ defer file.Close()
+
+ decoder := json.NewDecoder(file)
+ for {
+ var request proxifyRequest
+ err := decoder.Decode(&request)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return errors.Wrap(err, "could not decode json file")
+ }
+
+ rawRequest, err := types.ParseRawRequestWithURL(request.Request.Raw, request.URL)
+ if err != nil {
+ gologger.Warning().Msgf("jsonl: Could not parse raw request %s: %s\n", request.URL, err)
+ continue
+ }
+ resultsCb(rawRequest)
+ }
+ return nil
+}
diff --git a/pkg/input/formats/json/json_test.go b/pkg/input/formats/json/json_test.go
new file mode 100644
index 0000000000..b72bf4c197
--- /dev/null
+++ b/pkg/input/formats/json/json_test.go
@@ -0,0 +1,57 @@
+package json
+
+import (
+ "testing"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/stretchr/testify/require"
+)
+
+var expectedURLs = []string{
+ "https://ginandjuice.shop/",
+ "https://ginandjuice.shop/catalog/product?productId=1",
+ "https://ginandjuice.shop/resources/js/stockCheck.js",
+ "https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js",
+ "https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js",
+ "https://ginandjuice.shop/resources/js/stockCheck.js",
+ "https://ginandjuice.shop/catalog/product/stock",
+ "https://ginandjuice.shop/catalog/cart",
+ "https://ginandjuice.shop/catalog/product?productId=1",
+ "https://ginandjuice.shop/catalog/subscribe",
+ "https://ginandjuice.shop/blog",
+ "https://ginandjuice.shop/blog/?search=dadad&back=%2Fblog%2F",
+ "https://ginandjuice.shop/logger",
+ "https://ginandjuice.shop/blog/",
+ "https://ginandjuice.shop/blog/post?postId=3",
+ "https://ginandjuice.shop/about",
+ "https://ginandjuice.shop/my-account",
+ "https://ginandjuice.shop/login",
+ "https://ginandjuice.shop/login",
+ "https://ginandjuice.shop/login",
+ "https://ginandjuice.shop/my-account",
+ "https://ginandjuice.shop/catalog/cart",
+ "https://ginandjuice.shop/my-account",
+ "https://ginandjuice.shop/logout",
+ "https://ginandjuice.shop/",
+ "https://ginandjuice.shop/catalog",
+}
+
+func TestJSONFormatterParse(t *testing.T) {
+ format := New()
+
+ proxifyInputFile := "../testdata/ginandjuice.proxify.json"
+
+ var urls []string
+ err := format.Parse(proxifyInputFile, func(request *types.RequestResponse) bool {
+ urls = append(urls, request.URL.String())
+ return false
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(urls) != len(expectedURLs) {
+ t.Fatalf("invalid number of urls: %d", len(urls))
+ }
+ require.ElementsMatch(t, urls, expectedURLs)
+}
diff --git a/pkg/input/formats/openapi/examples.go b/pkg/input/formats/openapi/examples.go
new file mode 100644
index 0000000000..35c4292cb1
--- /dev/null
+++ b/pkg/input/formats/openapi/examples.go
@@ -0,0 +1,290 @@
+package openapi
+
+import (
+ "fmt"
+
+ "github.com/getkin/kin-openapi/openapi3"
+ "github.com/pkg/errors"
+)
+
+// From: https://github.com/danielgtaylor/apisprout/blob/master/example.go
+
+func getSchemaExample(schema *openapi3.Schema) (interface{}, bool) {
+ if schema.Example != nil {
+ return schema.Example, true
+ }
+
+ if schema.Default != nil {
+ return schema.Default, true
+ }
+
+ if schema.Enum != nil && len(schema.Enum) > 0 {
+ return schema.Enum[0], true
+ }
+ return nil, false
+}
+
+// stringFormatExample returns an example string based on the given format.
+// http://json-schema.org/latest/json-schema-validation.html#rfc.section.7.3
+func stringFormatExample(format string) string {
+ switch format {
+ case "date":
+ // https://tools.ietf.org/html/rfc3339
+ return "2018-07-23"
+ case "date-time":
+ // This is the date/time of API Sprout's first commit! :-)
+ return "2018-07-23T22:58:00-07:00"
+ case "time":
+ return "22:58:00-07:00"
+ case "email":
+ return "email@example.com"
+ case "hostname":
+ // https://tools.ietf.org/html/rfc2606#page-2
+ return "example.com"
+ case "ipv4":
+ // https://tools.ietf.org/html/rfc5737
+ return "198.51.100.0"
+ case "ipv6":
+ // https://tools.ietf.org/html/rfc3849
+ return "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
+ case "uri":
+ return "https://tools.ietf.org/html/rfc3986"
+ case "uri-template":
+ // https://tools.ietf.org/html/rfc6570
+ return "http://example.com/dictionary/{term:1}/{term}"
+ case "json-pointer":
+ // https://tools.ietf.org/html/rfc6901
+ return "#/components/parameters/term"
+ case "regex":
+ // https://stackoverflow.com/q/3296050/164268
+ return "/^1?$|^(11+?)\\1+$/"
+ case "uuid":
+ // https://www.ietf.org/rfc/rfc4122.txt
+ return "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
+ case "password":
+ return "********"
+ case "binary":
+ return "sagefuzzertest"
+ }
+ return ""
+}
+
+// excludeFromMode will exclude a schema if the mode is request and the schema
+// is read-only
+func excludeFromMode(schema *openapi3.Schema) bool {
+ if schema == nil {
+ return true
+ }
+
+ if schema.ReadOnly {
+ return true
+ }
+ return false
+}
+
+// isRequired checks whether a key is actually required.
+func isRequired(schema *openapi3.Schema, key string) bool {
+ for _, req := range schema.Required {
+ if req == key {
+ return true
+ }
+ }
+
+ return false
+}
+
+type cachedSchema struct {
+ pending bool
+ out interface{}
+}
+
+var (
+ // ErrRecursive is when a schema is impossible to represent because it infinitely recurses.
+ ErrRecursive = errors.New("Recursive schema")
+
+ // ErrNoExample is sent when no example was found for an operation.
+ ErrNoExample = errors.New("No example found")
+)
+
+func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedSchema) (out interface{}, err error) {
+ if ex, ok := getSchemaExample(schema); ok {
+ return ex, nil
+ }
+
+ cached, ok := cache[schema]
+ if !ok {
+ cached = &cachedSchema{
+ pending: true,
+ }
+ cache[schema] = cached
+ } else if cached.pending {
+ return nil, ErrRecursive
+ } else {
+ return cached.out, nil
+ }
+
+ defer func() {
+ cached.pending = false
+ cached.out = out
+ }()
+
+ // Handle combining keywords
+ if len(schema.OneOf) > 0 {
+ var ex interface{}
+ var err error
+
+ for _, candidate := range schema.OneOf {
+ ex, err = openAPIExample(candidate.Value, cache)
+ if err == nil {
+ break
+ }
+ }
+ return ex, err
+ }
+ if len(schema.AnyOf) > 0 {
+ var ex interface{}
+ var err error
+
+ for _, candidate := range schema.AnyOf {
+ ex, err = openAPIExample(candidate.Value, cache)
+ if err == nil {
+ break
+ }
+ }
+ return ex, err
+ }
+ if len(schema.AllOf) > 0 {
+ example := map[string]interface{}{}
+
+ for _, allOf := range schema.AllOf {
+ candidate, err := openAPIExample(allOf.Value, cache)
+ if err != nil {
+ return nil, err
+ }
+
+ value, ok := candidate.(map[string]interface{})
+ if !ok {
+ return nil, ErrNoExample
+ }
+
+ for k, v := range value {
+ example[k] = v
+ }
+ }
+ return example, nil
+ }
+
+ switch {
+ case schema.Type == "boolean":
+ return true, nil
+ case schema.Type == "number", schema.Type == "integer":
+ value := 0.0
+
+ if schema.Min != nil && *schema.Min > value {
+ value = *schema.Min
+ if schema.ExclusiveMin {
+ if schema.Max != nil {
+ // Make the value half way.
+ value = (*schema.Min + *schema.Max) / 2.0
+ } else {
+ value++
+ }
+ }
+ }
+
+ if schema.Max != nil && *schema.Max < value {
+ value = *schema.Max
+ if schema.ExclusiveMax {
+ if schema.Min != nil {
+ // Make the value half way.
+ value = (*schema.Min + *schema.Max) / 2.0
+ } else {
+ value--
+ }
+ }
+ }
+
+ if schema.MultipleOf != nil && int(value)%int(*schema.MultipleOf) != 0 {
+ value += float64(int(*schema.MultipleOf) - (int(value) % int(*schema.MultipleOf)))
+ }
+
+ if schema.Type == "integer" {
+ return int(value), nil
+ }
+ return value, nil
+ case schema.Type == "string":
+ if ex := stringFormatExample(schema.Format); ex != "" {
+ return ex, nil
+ }
+ example := "string"
+
+ for schema.MinLength > uint64(len(example)) {
+ example += example
+ }
+
+ if schema.MaxLength != nil && *schema.MaxLength < uint64(len(example)) {
+ example = example[:*schema.MaxLength]
+ }
+ return example, nil
+ case schema.Type == "array", schema.Items != nil:
+ example := []interface{}{}
+
+ if schema.Items != nil && schema.Items.Value != nil {
+ ex, err := openAPIExample(schema.Items.Value, cache)
+ if err != nil {
+ return nil, fmt.Errorf("can't get example for array item: %+v", err)
+ }
+
+ example = append(example, ex)
+
+ for uint64(len(example)) < schema.MinItems {
+ example = append(example, ex)
+ }
+ }
+ return example, nil
+ case schema.Type == "object", len(schema.Properties) > 0:
+ example := map[string]interface{}{}
+
+ for k, v := range schema.Properties {
+ if excludeFromMode(v.Value) {
+ continue
+ }
+
+ ex, err := openAPIExample(v.Value, cache)
+ if err == ErrRecursive {
+ if isRequired(schema, k) {
+ return nil, fmt.Errorf("can't get example for '%s': %+v", k, err)
+ }
+ } else if err != nil {
+ return nil, fmt.Errorf("can't get example for '%s': %+v", k, err)
+ } else {
+ example[k] = ex
+ }
+ }
+
+ if schema.AdditionalProperties.Has != nil && schema.AdditionalProperties.Schema != nil {
+ addl := schema.AdditionalProperties.Schema.Value
+
+ if !excludeFromMode(addl) {
+ ex, err := openAPIExample(addl, cache)
+ if err == ErrRecursive {
+ // We just won't add this if it's recursive.
+ } else if err != nil {
+ return nil, fmt.Errorf("can't get example for additional properties: %+v", err)
+ } else {
+ example["additionalPropertyName"] = ex
+ }
+ }
+ }
+ return example, nil
+ }
+ return nil, ErrNoExample
+}
+
+// generateExampleFromSchema creates an example structure from an OpenAPI 3 schema
+// object, which is an extended subset of JSON Schema.
+//
+// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#schemaObject
+func generateExampleFromSchema(schema *openapi3.Schema) (interface{}, error) {
+ return openAPIExample(schema, make(map[*openapi3.Schema]*cachedSchema)) // TODO: Use caching
+}
diff --git a/pkg/input/formats/openapi/generator.go b/pkg/input/formats/openapi/generator.go
new file mode 100644
index 0000000000..090b761c3a
--- /dev/null
+++ b/pkg/input/formats/openapi/generator.go
@@ -0,0 +1,461 @@
+package openapi
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "os"
+ "strings"
+
+ "github.com/clbanning/mxj/v2"
+ "github.com/getkin/kin-openapi/openapi3"
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
+ httpTypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/projectdiscovery/nuclei/v3/pkg/types"
+ errorutil "github.com/projectdiscovery/utils/errors"
+ "github.com/projectdiscovery/utils/generic"
+ mapsutil "github.com/projectdiscovery/utils/maps"
+ "github.com/valyala/fasttemplate"
+)
+
+const (
+ globalAuth = "globalAuth"
+)
+
+// GenerateRequestsFromSchema generates http requests from an OpenAPI 3.0 document object
+func GenerateRequestsFromSchema(schema *openapi3.T, opts formats.InputFormatOptions, callback formats.ParseReqRespCallback) error {
+ if len(schema.Servers) == 0 {
+ return errors.New("no servers found in openapi schema")
+ }
+
+ // new set of globalParams obtained from security schemes
+ globalParams := openapi3.NewParameters()
+
+ if len(schema.Security) > 0 {
+ params, err := GetGlobalParamsForSecurityRequirement(schema, &schema.Security)
+ if err != nil {
+ return err
+ }
+ globalParams = append(globalParams, params...)
+ }
+
+ // validate global param requirements
+ for _, param := range globalParams {
+ if val, ok := opts.Variables[param.Value.Name]; ok {
+ param.Value.Example = val
+ } else {
+ // if missing check for validation
+ if opts.SkipFormatValidation {
+ gologger.Verbose().Msgf("openapi: skipping all requests due to missing global auth parameter: %s\n", param.Value.Name)
+ return nil
+ } else {
+ // fatal error
+ gologger.Fatal().Msgf("openapi: missing global auth parameter: %s\n", param.Value.Name)
+ }
+ }
+ }
+
+ missingVarMap := make(map[string]struct{})
+ optionalVarMap := make(map[string]struct{})
+ missingParamValueCallback := func(param *openapi3.Parameter, opts *generateReqOptions) {
+ if !param.Required {
+ optionalVarMap[param.Name] = struct{}{}
+ return
+ }
+ missingVarMap[param.Name] = struct{}{}
+ }
+
+ for _, serverURL := range schema.Servers {
+ pathURL := serverURL.URL
+
+ for path, v := range schema.Paths.Map() {
+ // a path item can have parameters
+ ops := v.Operations()
+ requestPath := path
+ for method, ov := range ops {
+ if err := generateRequestsFromOp(&generateReqOptions{
+ requiredOnly: opts.RequiredOnly,
+ method: method,
+ pathURL: pathURL,
+ requestPath: requestPath,
+ op: ov,
+ schema: schema,
+ globalParams: globalParams,
+ reqParams: v.Parameters,
+ opts: opts,
+ callback: callback,
+ missingParamValueCallback: missingParamValueCallback,
+ }); err != nil {
+ gologger.Warning().Msgf("Could not generate requests from op: %s\n", err)
+ }
+ }
+ }
+ }
+
+ if len(missingVarMap) > 0 && !opts.SkipFormatValidation {
+ gologger.Error().Msgf("openapi: Found %d missing parameters, use -skip-format-validation flag to skip requests or update missing parameters generated in %s file,you can also specify these vars using -var flag in (key=value) format\n", len(missingVarMap), formats.DefaultVarDumpFileName)
+ gologger.Verbose().Msgf("openapi: missing params: %+v", mapsutil.GetSortedKeys(missingVarMap))
+ if config.CurrentAppMode == config.AppModeCLI {
+ // generate var dump file
+ vars := &formats.OpenAPIParamsCfgFile{}
+ for k := range missingVarMap {
+ vars.Var = append(vars.Var, k+"=")
+ }
+ vars.OptionalVars = mapsutil.GetSortedKeys(optionalVarMap)
+ if err := formats.WriteOpenAPIVarDumpFile(vars); err != nil {
+ gologger.Error().Msgf("openapi: could not write params file: %s\n", err)
+ }
+ // exit with status code 1
+ os.Exit(1)
+ }
+ }
+
+ return nil
+}
+
+type generateReqOptions struct {
+ // requiredOnly specifies whether to generate only required fields
+ requiredOnly bool
+ // method is the http method to use
+ method string
+ // pathURL is the base url to use
+ pathURL string
+ // requestPath is the path to use
+ requestPath string
+ // schema is the openapi schema to use
+ schema *openapi3.T
+ // op is the operation to use
+ op *openapi3.Operation
+ // post request generation callback
+ callback formats.ParseReqRespCallback
+
+ // global parameters
+ globalParams openapi3.Parameters
+ // requestparams map
+ reqParams openapi3.Parameters
+ // global var map
+ opts formats.InputFormatOptions
+ // missingVar Callback
+ missingParamValueCallback func(param *openapi3.Parameter, opts *generateReqOptions)
+}
+
+// generateRequestsFromOp generates requests from an operation and some other data
+// about an OpenAPI Schema Path and Method object.
+//
+// It also accepts an optional requiredOnly flag which if specified, only returns the fields
+// of the structure that are required. If false, all fields are returned.
+func generateRequestsFromOp(opts *generateReqOptions) error {
+ req, err := http.NewRequest(opts.method, opts.pathURL+opts.requestPath, nil)
+ if err != nil {
+ return errors.Wrap(err, "could not make request")
+ }
+
+ reqParams := opts.reqParams
+ if reqParams == nil {
+ reqParams = openapi3.NewParameters()
+ }
+ // add existing req params
+ reqParams = append(reqParams, opts.op.Parameters...)
+ // check for endpoint specific auth
+ if opts.op.Security != nil {
+ params, err := GetGlobalParamsForSecurityRequirement(opts.schema, opts.op.Security)
+ if err != nil {
+ return err
+ }
+ reqParams = append(reqParams, params...)
+ } else {
+ reqParams = append(reqParams, opts.globalParams...)
+ }
+
+ query := url.Values{}
+ for _, parameter := range reqParams {
+ value := parameter.Value
+
+ // paramValue or default value to use
+ var paramValue interface{}
+
+ // accept override from global variables
+ if val, ok := opts.opts.Variables[value.Name]; ok {
+ paramValue = val
+ } else if value.Schema.Value.Default != nil {
+ paramValue = value.Schema.Value.Default
+ } else if value.Schema.Value.Example != nil {
+ paramValue = value.Schema.Value.Example
+ } else if value.Schema.Value.Enum != nil && len(value.Schema.Value.Enum) > 0 {
+ paramValue = value.Schema.Value.Enum[0]
+ } else {
+ if !opts.opts.SkipFormatValidation {
+ if opts.missingParamValueCallback != nil {
+ opts.missingParamValueCallback(value, opts)
+ }
+ // skip request if param in path else skip this param only
+ if value.Required {
+ // gologger.Verbose().Msgf("skipping request [%s] %s due to missing value (%v)\n", opts.method, opts.requestPath, value.Name)
+ return nil
+ } else {
+ // if it is in path then remove it from path
+ opts.requestPath = strings.Replace(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "", -1)
+ if !opts.opts.RequiredOnly {
+ gologger.Verbose().Msgf("openapi: skipping optional param (%s) in (%v) in request [%s] %s due to missing value (%v)\n", value.Name, value.In, opts.method, opts.requestPath, value.Name)
+ }
+ continue
+ }
+ }
+ exampleX, err := generateExampleFromSchema(value.Schema.Value)
+ if err != nil {
+ // when failed to generate example
+ // skip request if param in path else skip this param only
+ if value.Required {
+ gologger.Verbose().Msgf("openapi: skipping request [%s] %s due to missing value (%v)\n", opts.method, opts.requestPath, value.Name)
+ return nil
+ } else {
+ // if it is in path then remove it from path
+ opts.requestPath = strings.Replace(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "", -1)
+ if !opts.opts.RequiredOnly {
+ gologger.Verbose().Msgf("openapi: skipping optinal param (%s) in (%v) in request [%s] %s due to missing value (%v)\n", value.Name, value.In, opts.method, opts.requestPath, value.Name)
+ }
+ continue
+ }
+ }
+ paramValue = exampleX
+ }
+ if opts.requiredOnly && !value.Required {
+ // remove them from path if any
+ opts.requestPath = strings.Replace(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "", -1)
+ continue // Skip this parameter if it is not required and we want only required ones
+ }
+
+ switch value.In {
+ case "query":
+ query.Set(value.Name, types.ToString(paramValue))
+ case "header":
+ req.Header.Set(value.Name, types.ToString(paramValue))
+ case "path":
+ opts.requestPath = fasttemplate.ExecuteStringStd(opts.requestPath, "{", "}", map[string]interface{}{
+ value.Name: types.ToString(paramValue),
+ })
+ case "cookie":
+ req.AddCookie(&http.Cookie{Name: value.Name, Value: types.ToString(paramValue)})
+ }
+ }
+ req.URL.RawQuery = query.Encode()
+ req.URL.Path = opts.requestPath
+
+ if opts.op.RequestBody != nil {
+ for content, value := range opts.op.RequestBody.Value.Content {
+ cloned := req.Clone(req.Context())
+
+ example, err := generateExampleFromSchema(value.Schema.Value)
+ if err != nil {
+ continue
+ }
+
+ // var body string
+ switch content {
+ case "application/json":
+ if marshalled, err := json.Marshal(example); err == nil {
+ // body = string(marshalled)
+ cloned.Body = io.NopCloser(bytes.NewReader(marshalled))
+ cloned.ContentLength = int64(len(marshalled))
+ cloned.Header.Set("Content-Type", "application/json")
+ }
+ case "application/xml":
+ exampleVal := mxj.Map(example.(map[string]interface{}))
+
+ if marshalled, err := exampleVal.Xml(); err == nil {
+ // body = string(marshalled)
+ cloned.Body = io.NopCloser(bytes.NewReader(marshalled))
+ cloned.ContentLength = int64(len(marshalled))
+ cloned.Header.Set("Content-Type", "application/xml")
+ } else {
+ gologger.Warning().Msgf("openapi: could not encode xml")
+ }
+ case "application/x-www-form-urlencoded":
+ if values, ok := example.(map[string]interface{}); ok {
+ cloned.Form = url.Values{}
+ for k, v := range values {
+ cloned.Form.Set(k, types.ToString(v))
+ }
+ encoded := cloned.Form.Encode()
+ cloned.ContentLength = int64(len(encoded))
+ // body = encoded
+ cloned.Body = io.NopCloser(strings.NewReader(encoded))
+ cloned.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ }
+ case "multipart/form-data":
+ if values, ok := example.(map[string]interface{}); ok {
+ buffer := &bytes.Buffer{}
+ multipartWriter := multipart.NewWriter(buffer)
+ for k, v := range values {
+ // This is a file if format is binary, otherwise field
+ if property, ok := value.Schema.Value.Properties[k]; ok && property.Value.Format == "binary" {
+ if writer, err := multipartWriter.CreateFormFile(k, k); err == nil {
+ _, _ = writer.Write([]byte(types.ToString(v)))
+ }
+ } else {
+ _ = multipartWriter.WriteField(k, types.ToString(v))
+ }
+ }
+ multipartWriter.Close()
+ // body = buffer.String()
+ cloned.Body = io.NopCloser(buffer)
+ cloned.ContentLength = int64(len(buffer.Bytes()))
+ cloned.Header.Set("Content-Type", multipartWriter.FormDataContentType())
+ }
+ case "text/plain":
+ str := types.ToString(example)
+ // body = str
+ cloned.Body = io.NopCloser(strings.NewReader(str))
+ cloned.ContentLength = int64(len(str))
+ cloned.Header.Set("Content-Type", "text/plain")
+ case "application/octet-stream":
+ str := types.ToString(example)
+ if str == "" {
+ // use two strings
+ str = "string1\nstring2"
+ }
+ if value.Schema != nil && generic.EqualsAny(value.Schema.Value.Format, "bindary", "byte") {
+ cloned.Body = io.NopCloser(bytes.NewReader([]byte(str)))
+ cloned.ContentLength = int64(len(str))
+ cloned.Header.Set("Content-Type", "application/octet-stream")
+ } else {
+ // use string placeholder
+ cloned.Body = io.NopCloser(strings.NewReader(str))
+ cloned.ContentLength = int64(len(str))
+ cloned.Header.Set("Content-Type", "text/plain")
+ }
+ default:
+ gologger.Verbose().Msgf("openapi: no correct content type found for body: %s\n", content)
+ // LOG: return errors.New("no correct content type found for body")
+ continue
+ }
+
+ dumped, err := httputil.DumpRequestOut(cloned, true)
+ if err != nil {
+ return errors.Wrap(err, "could not dump request")
+ }
+
+ rr, err := httpTypes.ParseRawRequestWithURL(string(dumped), cloned.URL.String())
+ if err != nil {
+ return errors.Wrap(err, "could not parse raw request")
+ }
+ opts.callback(rr)
+ continue
+ }
+ }
+ if opts.op.RequestBody != nil {
+ return nil
+ }
+
+ dumped, err := httputil.DumpRequestOut(req, true)
+ if err != nil {
+ return errors.Wrap(err, "could not dump request")
+ }
+
+ rr, err := httpTypes.ParseRawRequestWithURL(string(dumped), req.URL.String())
+ if err != nil {
+ return errors.Wrap(err, "could not parse raw request")
+ }
+ opts.callback(rr)
+ return nil
+}
+
+// GetGlobalParamsForSecurityRequirement returns the global parameters for a security requirement
+func GetGlobalParamsForSecurityRequirement(schema *openapi3.T, requirement *openapi3.SecurityRequirements) ([]*openapi3.ParameterRef, error) {
+ globalParams := openapi3.NewParameters()
+ if len(schema.Components.SecuritySchemes) == 0 {
+ return nil, errorutil.NewWithTag("openapi", "security requirements (%+v) without any security schemes found in openapi file", schema.Security)
+ }
+ found := false
+ // this api is protected for each security scheme pull its corresponding scheme
+schemaLabel:
+ for _, security := range *requirement {
+ for name := range security {
+ if scheme, ok := schema.Components.SecuritySchemes[name]; ok {
+ found = true
+ param, err := GenerateParameterFromSecurityScheme(scheme)
+ if err != nil {
+ return nil, err
+
+ }
+ globalParams = append(globalParams, &openapi3.ParameterRef{Value: param})
+ continue schemaLabel
+ }
+ }
+ if !found && len(security) > 1 {
+ // if this is case then both security schemes are required
+ return nil, errorutil.NewWithTag("openapi", "security requirement (%+v) not found in openapi file", security)
+ }
+ }
+ if !found {
+ return nil, errorutil.NewWithTag("openapi", "security requirement (%+v) not found in openapi file", requirement)
+ }
+
+ return globalParams, nil
+}
+
+// generateExampleFromSchema generates an example from a schema object
+func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*openapi3.Parameter, error) {
+ if !generic.EqualsAny(scheme.Value.Type, "http", "apiKey") {
+ return nil, errorutil.NewWithTag("openapi", "unsupported security scheme type (%s) found in openapi file", scheme.Value.Type)
+ }
+ if scheme.Value.Type == "http" {
+ // check scheme
+ if !generic.EqualsAny(scheme.Value.Scheme, "basic", "bearer") {
+ return nil, errorutil.NewWithTag("openapi", "unsupported security scheme (%s) found in openapi file", scheme.Value.Scheme)
+ }
+ if scheme.Value.Name == "" {
+ return nil, errorutil.NewWithTag("openapi", "security scheme (%s) name is empty", scheme.Value.Scheme)
+ }
+ // create parameters using the scheme
+ switch scheme.Value.Scheme {
+ case "basic":
+ h := openapi3.NewHeaderParameter(scheme.Value.Name)
+ h.Required = true
+ h.Description = globalAuth // differentiator for normal variables and global auth
+ return h, nil
+ case "bearer":
+ h := openapi3.NewHeaderParameter(scheme.Value.Name)
+ h.Required = true
+ h.Description = globalAuth // differentiator for normal variables and global auth
+ return h, nil
+ }
+
+ }
+ if scheme.Value.Type == "apiKey" {
+ // validate name and in
+ if scheme.Value.Name == "" {
+ return nil, errorutil.NewWithTag("openapi", "security scheme (%s) name is empty", scheme.Value.Type)
+ }
+ if !generic.EqualsAny(scheme.Value.In, "query", "header", "cookie") {
+ return nil, errorutil.NewWithTag("openapi", "unsupported security scheme (%s) in (%s) found in openapi file", scheme.Value.Type, scheme.Value.In)
+ }
+ // create parameters using the scheme
+ switch scheme.Value.In {
+ case "query":
+ q := openapi3.NewQueryParameter(scheme.Value.Name)
+ q.Required = true
+ q.Description = globalAuth // differentiator for normal variables and global auth
+ return q, nil
+ case "header":
+ h := openapi3.NewHeaderParameter(scheme.Value.Name)
+ h.Required = true
+ h.Description = globalAuth // differentiator for normal variables and global auth
+ return h, nil
+ case "cookie":
+ c := openapi3.NewCookieParameter(scheme.Value.Name)
+ c.Required = true
+ c.Description = globalAuth // differentiator for normal variables and global auth
+ return c, nil
+ }
+ }
+ return nil, errorutil.NewWithTag("openapi", "unsupported security scheme type (%s) found in openapi file", scheme.Value.Type)
+}
diff --git a/pkg/input/formats/openapi/openapi.go b/pkg/input/formats/openapi/openapi.go
new file mode 100644
index 0000000000..afbe379fd2
--- /dev/null
+++ b/pkg/input/formats/openapi/openapi.go
@@ -0,0 +1,39 @@
+package openapi
+
+import (
+ "github.com/getkin/kin-openapi/openapi3"
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
+)
+
+// OpenAPIFormat is a OpenAPI Schema File parser
+type OpenAPIFormat struct {
+ opts formats.InputFormatOptions
+}
+
+// New creates a new OpenAPI format parser
+func New() *OpenAPIFormat {
+ return &OpenAPIFormat{}
+}
+
+var _ formats.Format = &OpenAPIFormat{}
+
+// Name returns the name of the format
+func (j *OpenAPIFormat) Name() string {
+ return "openapi"
+}
+
+func (j *OpenAPIFormat) SetOptions(options formats.InputFormatOptions) {
+ j.opts = options
+}
+
+// Parse parses the input and calls the provided callback
+// function for each RawRequest it discovers.
+func (j *OpenAPIFormat) Parse(input string, resultsCb formats.ParseReqRespCallback) error {
+ loader := openapi3.NewLoader()
+ schema, err := loader.LoadFromFile(input)
+ if err != nil {
+ return errors.Wrap(err, "could not decode openapi 3.0 schema")
+ }
+ return GenerateRequestsFromSchema(schema, j.opts, resultsCb)
+}
diff --git a/pkg/input/formats/openapi/openapi_test.go b/pkg/input/formats/openapi/openapi_test.go
new file mode 100644
index 0000000000..f48385a808
--- /dev/null
+++ b/pkg/input/formats/openapi/openapi_test.go
@@ -0,0 +1,63 @@
+package openapi
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/stretchr/testify/require"
+)
+
+const baseURL = "http://hackthebox:5000"
+
+var methodToURLs = map[string][]string{
+ "GET": {
+ "{{baseUrl}}/createdb",
+ "{{baseUrl}}/",
+ "{{baseUrl}}/users/v1/John.Doe",
+ "{{baseUrl}}/users/v1",
+ "{{baseUrl}}/users/v1/_debug",
+ "{{baseUrl}}/books/v1",
+ "{{baseUrl}}/books/v1/bookTitle77",
+ },
+ "POST": {
+ "{{baseUrl}}/users/v1/register",
+ "{{baseUrl}}/users/v1/login",
+ "{{baseUrl}}/books/v1",
+ },
+ "PUT": {
+ "{{baseUrl}}/users/v1/name1/email",
+ "{{baseUrl}}/users/v1/name1/password",
+ },
+ "DELETE": {
+ "{{baseUrl}}/users/v1/name1",
+ },
+}
+
+func TestOpenAPIParser(t *testing.T) {
+ format := New()
+
+ proxifyInputFile := "../testdata/openapi.yaml"
+
+ gotMethodsToURLs := make(map[string][]string)
+
+ err := format.Parse(proxifyInputFile, func(rr *types.RequestResponse) bool {
+ gotMethodsToURLs[rr.Request.Method] = append(gotMethodsToURLs[rr.Request.Method],
+ strings.Replace(rr.URL.String(), baseURL, "{{baseUrl}}", 1))
+ return false
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(gotMethodsToURLs) != len(methodToURLs) {
+ t.Fatalf("invalid number of methods: %d", len(gotMethodsToURLs))
+ }
+
+ for method, urls := range gotMethodsToURLs {
+ if len(urls) != len(methodToURLs[method]) {
+ t.Fatalf("invalid number of urls for method %s: %d", method, len(urls))
+ }
+ require.ElementsMatch(t, urls, methodToURLs[method], "invalid urls for method %s", method)
+ }
+}
diff --git a/pkg/input/formats/swagger/swagger.go b/pkg/input/formats/swagger/swagger.go
new file mode 100644
index 0000000000..2828bb293e
--- /dev/null
+++ b/pkg/input/formats/swagger/swagger.go
@@ -0,0 +1,69 @@
+package swagger
+
+import (
+ "encoding/json"
+ "os"
+ "path"
+
+ "github.com/getkin/kin-openapi/openapi2"
+ "github.com/getkin/kin-openapi/openapi3"
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats/openapi"
+ "gopkg.in/yaml.v2"
+
+ "github.com/getkin/kin-openapi/openapi2conv"
+)
+
+// SwaggerFormat is a Swagger Schema File parser
+type SwaggerFormat struct {
+ opts formats.InputFormatOptions
+}
+
+// New creates a new Swagger format parser
+func New() *SwaggerFormat {
+ return &SwaggerFormat{}
+}
+
+var _ formats.Format = &SwaggerFormat{}
+
+// Name returns the name of the format
+func (j *SwaggerFormat) Name() string {
+ return "swagger"
+}
+
+func (j *SwaggerFormat) SetOptions(options formats.InputFormatOptions) {
+ j.opts = options
+}
+
+// Parse parses the input and calls the provided callback
+// function for each RawRequest it discovers.
+func (j *SwaggerFormat) Parse(input string, resultsCb formats.ParseReqRespCallback) error {
+ file, err := os.Open(input)
+ if err != nil {
+ return errors.Wrap(err, "could not open data file")
+ }
+ defer file.Close()
+
+ schemav2 := &openapi2.T{}
+ ext := path.Ext(input)
+
+ if ext == ".yaml" || ext == ".yml" {
+ err = yaml.NewDecoder(file).Decode(schemav2)
+ } else {
+ err = json.NewDecoder(file).Decode(schemav2)
+ }
+ if err != nil {
+ return errors.Wrap(err, "could not decode openapi 2.0 schema")
+ }
+ schema, err := openapi2conv.ToV3(schemav2)
+ if err != nil {
+ return errors.Wrap(err, "could not convert openapi 2.0 schema to 3.0")
+ }
+ loader := openapi3.NewLoader()
+ err = loader.ResolveRefsIn(schema, nil)
+ if err != nil {
+ return errors.Wrap(err, "could not resolve openapi schema references")
+ }
+ return openapi.GenerateRequestsFromSchema(schema, j.opts, resultsCb)
+}
diff --git a/pkg/input/formats/swagger/swagger_test.go b/pkg/input/formats/swagger/swagger_test.go
new file mode 100644
index 0000000000..601c20d94b
--- /dev/null
+++ b/pkg/input/formats/swagger/swagger_test.go
@@ -0,0 +1,34 @@
+package swagger
+
+import (
+ "testing"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSwaggerAPIParser(t *testing.T) {
+ format := New()
+
+ proxifyInputFile := "../testdata/swagger.yaml"
+
+ var gotMethodsToURLs []string
+
+ err := format.Parse(proxifyInputFile, func(request *types.RequestResponse) bool {
+ gotMethodsToURLs = append(gotMethodsToURLs, request.URL.String())
+ return false
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(gotMethodsToURLs) != 2 {
+ t.Fatalf("invalid number of methods: %d", len(gotMethodsToURLs))
+ }
+
+ expectedURLs := []string{
+ "https://localhost/users",
+ "https://localhost/users/1?test=asc",
+ }
+ require.ElementsMatch(t, gotMethodsToURLs, expectedURLs, "could not get swagger urls")
+}
diff --git a/pkg/input/formats/testdata/burp.xml b/pkg/input/formats/testdata/burp.xml
new file mode 100644
index 0000000000..847963419d
--- /dev/null
+++ b/pkg/input/formats/testdata/burp.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]>
+
+ -
+
Sat Sep 30 20:11:32 IST 2023
+
+ localhost
+ 8087
+ http
+
+
+ null
+
+ 200
+ 152
+ JSON
+
+
+
+ -
+
Sat Sep 30 20:08:54 IST 2023
+
+ google.com
+ 80
+ http
+
+
+ null
+
+ 301
+ 792
+ HTML
+
+
+
+
\ No newline at end of file
diff --git a/pkg/input/formats/testdata/ginandjuice.proxify.json b/pkg/input/formats/testdata/ginandjuice.proxify.json
new file mode 100644
index 0000000000..64282b6624
--- /dev/null
+++ b/pkg/input/formats/testdata/ginandjuice.proxify.json
@@ -0,0 +1,26 @@
+{"timestamp":"2023-09-07T21:03:38+05:30","url":"https://ginandjuice.shop/","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7; AWSALBCORS=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"none","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/","scheme":"https"},"raw":"GET / HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7; AWSALBCORS=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: none\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2127","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:38 GMT","Set-Cookie":"AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/, AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd\u001a\ufffdr\ufffd8\ufffd\ufffd=\ufffd\ufffdwGa\u0006\ufffdMR\ufffdtHrs\ufffd@\ufffd\n-\u0003\u0003s\ufffdad[\ufffdUd\ufffdXr\ufffd\u001f\ufffd^\ufffd\ufffd\ufffdV\ufffd\ufffd:\ufffd\u001d;\ufffd\ufffdp3t:m$\ufffd\ufffdV\ufffd\ufffdRF?\ufffd]\ufffd\ufffd\ufffd\ufffd\ufffd\t\ufffdU\ufffd\u0026?\ufffd\ufffd?\u0004?\ufffd\ufffd\ufffd\ufffd~4CF\ufffd'\u0014gd:\ufffd2\"E\ufffd\u0005Dz\u000c\ufffd\u001a\ufffdd^ \ufffd'\u0003\ufffd_\ufffds3у\t\ufffd\u00116\ufffd\ufffd`DƄ\ufffd6b\ufffd\u0004\u0010\ufffdo\ufffdLB\ufffd\u0013H\ufffdㄌ\ufffd\u0019%\ufffdTd\ufffdA\ufffd\ufffd\ufffdp5v\ufffd4T\ufffd8$3\u001a\u0010\ufffd\u000c\ufffd\ufffd\\\ufffd\ufffd\u0005\u000ea\u0007F\ufffd\\8\u0015j2\ufffdh\ufffd\ufffd̂\ufffdSa\ufffdZ\ufffd\u0000\u0007\ufffd\u0007\ufffd\u0008\u0013i\u0002\ufffd{\ufffdҙ\ufffd\u003c\ufffdѝ\ufffd\u001b\ufffdd\u00072\ufffdH\ufffdd\ufffd|Q\ufffd5\ufffda;\ufffd\ufffdQ\ufffd\u003c\ufffd\u0019\ufffd\u003e\ufffd\ufffdc\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdɹH\u0008r\ufffd3\ufffd\ufffd\u001d\ufffd\ufffd\ufffdЋ\u001c\ufffd\ufffd\ufffd\ufffd\"\u001dy\u0016\ufffdjQ\ufffd\ufffd\u0017a\ufffdx\ufffd\ufffd4\ufffd\u0010\u000c\ufffd\u000c\ufffdp\ufffdT5^\u0011\ufffd=\n\t\u0014\u0015\u001c\u0005\u000cK9v\ufffd\ufffd\ufffd!\ufffd\ufffd\ufffd\ufffd\u001a\ufffdAJ7\ufffd\ufffd\ufffdۘJ\u0004\ufffd\u0018\ufffd\ufffdQ\ufffddX\u0011V\ufffdY\ufffd8|\u0006\ufffd\ufffd9\ufffd\u0011\ufffd\ufffdh\ufffdͮ\ufffd\u000f\ufffd8\t\ufffdTdH\u0011\ufffd(\ufffd4\ufffd\u001d\ufffd\ufffd\ufffd\ufffd\u0002\ufffd2\ufffd\n;\ufffd\ufffd\u0002s\ufffdW\ufffd\ufffdH\ufffdxy\u0014m\ufffd\u000e\u0012\u003c\u0011`T\ufffdL\ufffdi\ufffd㡘\ufffd\u003e~\ufffd\ufffdGc\ufffd_.\ufffd\ufffd\ufffd\ufffd`e5\ufffd\ufffd\ufffd\ufffdЮ.\ufffda\ufffdW\ufffd\ufffdV\ufffd\ufffdt\ufffd\ufffd)\ufffd[\ufffd\ufffd\ufffd\u001dT\ufffdke8\\\u001d\u001e\ufffdCL\ufffd\ufffd\u001a#\ufffd\ufffd\ufffd\ufffdb\ufffd\ufffd\ufffd?{\ufffd\ufffd\ufffd)\ufffd¦\ufffd3Ƃ\ufffdx|po\ufffd+#X\ufffd\u000cz \ufffdT\ufffd\ufffd\u001f\ufffd\ufffd\ufffdA\ufffdܰf\ufffdș\\\ufffd\n\u0015`֨\ufffd\u0003R\u0002\ufffd\ufffd\u0018\ufffd\ufffd\u000c\u0026\r\ufffd\ufffd1\u0017o\ufffd^\u0000\ufffdZ\ufffdV[\u003c\ufffdf\ufffd\ufffdltPCV\ufffd\ufffd\u0005\ufffd!\u0005S\u0000\ufffd\ufffd\ufffd\u0011\ufffdQ\ufffd\ufffd\ufffd!SCN\ufffd\ufffd\ufffd2\u0011\t\ufffdS\ufffdo \ufffdᆅ\u001bR\ufffda\ufffd\u0016\ufffd\u000c\u000e\ufffd\r\ufffd,!r\ufffd0m\ufffd\ufffd\ufffd\ufffd\t\ufffdtXm\ufffd\u0012\ufffd\u0019MpV\ufffdQ\ufffd\ufffdn\ufffdd\ufffd\u001d\ufffd\u0000\ufffd\ufffd\ufffd~\ufffd\u0014\u0004\u0005+P\ufffd\u0003\ufffd\ufffd '\ufffd\ufffdL\ufffdy\ufffdd\ufffdPV\ufffdy]6\ufffd=w\ufffda\ufffd1\ufffd\ufffd\ufffd\ufffd¾ȕ3\ufffd\ufffd~\ufffdDV|#\ufffdF^\ufffdnmB\ufffd\ufffd\ufffd\ufffd{6\"\u001c\u0004\"\ufffdʥAE*I\ufffd\ufffd\ufffd\ufffd\u001d\ufffd,\ufffd\ufffd \ufffd-\u0016o\ufffd)Xx\ufffd\ufffd\u0001\ufffd\u0002\ufffd\u0007п\ufffd\ufffd\u0004\ufffd\u0005d\ufffdd5\ufffd.\ufffd-\ufffd\ufffd\rp\ufffdco\u001e\ufffd\u001c\ufffd\u0007\ufffd\u0010\u0011\ufffd\ufffd3džn'\ufffd\ufffd/\ufffd/\u000bT~\ufffd\u0013\ufffd\ufffd\ufffdݝ\ufffdW\u0018l\ufffd\ufffd5k-\u0003\ufffd\ufffdW\ufffd\ufffd\ufffd\ufffdwXf\ufffd\ufffd֫Q\ufffd\ufffd\ufffdP\ufffd\ufffdn;\u001f\ufffd\ufffd\ufffd\ufffd\u0012Q\ufffd\u0008\ufffd\ufffdWf3\u001aE\ufffdB\ufffd\ufffdY\u001e\ufffdq\u0000\ufffd\ufffd6*\ufffd\ufffdƼאyk\ufffdk\ufffd\ufffd5(\ufffd\ufffd\ufffdPѝH\u000c\u0005vK\u0015\ufffd@\ufffd^\ufffd\ufffd\ufffd\u0002\ufffd\u0026\ufffd;\ufffd\ufffd\u000c\ufffd\r\ufffdKA\u000bE\ufffde\ufffd[F\ufffd\u0026D\ufffd.7\ufffd\ufffd\ufffdl\n\r[+m\ufffd\ufffd7\n\ufffd\ufffd\ufffd\"g?n\ufffd\ufffd\ufffd\u0000\u0006aƠ\ufffdhI\ufffd\r\u0015\ufffd\u0006\ufffd\ufffd\u001bY\u0010\ufffdʈfb\u001b\ufffd\u00151/\ufffdζv\u0026+\ufffd\ufffd\ufffd\u0014\ufffd:\u0005\ufffd\u0005\ufffdR\ufffd\ufffd\ufffd\u0000\ufffd\ufffd\ufffdV\u0013h\u0012\ufffd(4\u001b\ufffdAe)\ufffd{\ufffd\ufffd\u0010\ufffdX\"h\u003c\u0019\ufffd;\u001a\n\ufffdc(\"\ufffd\ufffd\u0012\ufffd)\ufffd(w\ufffd۶\ufffdҒ\ufffdvG\ufffd\u0004\ufffd\u001fyǺ\u001a\ufffd\ufffdY9-\ufffd\ufffd\u0000\ufffd\u0014\ufffd?ڒ=\ufffd%\u001d:F; 3\ufffdr\ufffdu\u0019\ufffd\u0010'$\ufffd\ufffd\ufffd\u0016eո\u001b[\ufffd\u0026+-m\ufffd\u0002\ufffd\ufffd\ufffdJ\ufffd\ufffdx\u0019N\ufffd\ufffd\ufffd\ufffd\u0005\u001b%\ufffd\ufffd0\ufffd\ufffd\ufffdJV\ufffd(\ufffd?\u000f\ufffd\ufffd\u000et\u000c-\ufffdD\ufffd%\ufffdIW\ufffd\ufffd\ufffd\u0016\u0014\ufffd6\ufffd^\n\ufffdꦞ|$!\ufffdg\ufffd\ufffd\u003c\ufffd\ufffdW\u003c\ufffd\\-\ufffd\ufffd\u0013\ufffd\ufffdNE\ufffd\t\ufffd\ufffd \u0004\u000ew\ufffdy#\ufffdfX_+\u000cw\ufffd\ufffd\ufffd\ufffd\ufffdT\u0005\ufffd0~\u001d\u001e\ufffd\u001e\u001c6\ufffd\ufffd\ufffdT\ufffd\ufffd\ufffd\ufffdԐ\ufffd\ufffdɮĺ\u0015\u0002\u001d,`\ufffd'\u000b\ufffd\ufffdwT\ufffd\r\ufffd\ufffdo\u001d\u001d/\ufffd\ufffdQ\ufffd\ufffd\ufffd*\ufffd\ufffdQ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdR\ufffd\ufffd\ufffd\n\ufffd\ufffdT\ufffdK\ufffdg\u000c\u0017\ufffd\ufffd\ufffd\ufffd\ufffdA\ufffd\ufffd\ufffd\ufffdTsKYeA\ufffd'\ufffdu#\ufffdWU\ufffd\ufffdZv\ufffd\ufffd\u000c\ufffd\ufffd\ufffdK\ufffd\\CsSю\ufffd\ufffd\ufffd@\ufffd\ufffd\ufffdC\ufffd\ufffd^\ufffdd\ufffdKX\ufffd=\ufffd]\ufffd\ufffd\ufffdޅ\ufffd\ufffd\ufffd\ufffdOt\ufffdiV\ufffd3\\\ufffdm\u000fZ9[1\u001e\ufffd!]\ufffd\ufffdF\ufffdm\u001c\u001b\ufffd\ufffda\u0007\ufffd\ufffdj\u0012\u0006\ufffd\ufffd\n\ufffd\ufffd\ufffd@\ufffd;\u0008\ufffd-\u0014\ufffd\ufffd\"I\u0019\ufffdBj/\"=\ufffd\ufffd\"\ufffd\ufffd\ufffd\ufffd\ufffd\u0007\ufffd\ufffda\ufffd\ufffd|\ufffd['l\ufffd+\ufffd\ufffd\n\u0001=\ufffd;Ϡ\ufffd\ufffdon\ufffd*R\ufffd\ufffdԤnRfd\ufffdoD\ufffdܛo\ufffd\u0004\ufffd\u0011\ufffd\u00168\ufffd\u0002^\u0011H\u001f(\ufffdҾ0a\ufffd\\$s_\ufffd\ufffd\u003eA\\̷\ufffd\t\ufffd\ufffd/\u0004\ufffdo\ufffd\ufffd\ufffd\ufffd\ufffdoNC\ufffd\u0002\ufffd$9\ufffdn\ufffd\ufffd\ufffd\u0010\ufffd!\ufffd6\ufffd\ufffd\u0014s\ufffd0\ufffd\ufffdo^\ufffdȗ\ufffd\ufffd\ufffd\ufffd`'2\ufffd\ufffd\ufffd\u0012bX?}\u0018n\u0000\u000eV\ufffdc(\ufffd\ufffd\ufffd(\u0010\ufffda_d\ufffd߀\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdt\u001b\ufffd7ʃ\ufffdW\ufffd\ufffda_A\ufffds-\u000fj\u001e\u0011kem\ufffd\u0000\u0017\ufffd\ufffd\ufffd\ufffdA!\ufffd\u00057!*\u0016\ufffd5\ufffdQ\ufffdNac\u0003\ufffd\ufffd\ufffd\ufffd@\ufffd\ufffdP\ufffd\ufffd\n\ufffd\ufffd9\ufffd\u0019\ufffd\ufffd摔$\ufffd\u0017\ufffdK\ufffd\ufffd\u0008\ufffd\u001d\ufffdX\ufffdP7\ufffdO\ufffd\u0014\u000eC8m[\ufffdSKމi\u0018\u0012\ufffd\ufffdó̦\u000eҭ\u001e\u000c\ufffdtv\ufffd\ufffd\ufffdɻ\ufffd\ufffd\ufffd_\u001f\u001e\ufffdǟs\ufffd䃺\u000e/_\ufffd\ufffd\ufffd\ufffd\ufffde\ufffd~e@Y\ufffd\u00035ۂH\u0012\ufffd\u0026o\u0016\ufffd\u0019yv\ufffd6\ufffd\ufffd\ufffd\ufffdQX \ufffdTp\ufffd\ufffd\ufffd\ufffd\u001fK\ufffdf\ufffdn9a\ufffd\ufffd$\ufffd✂\u0007\ufffd\u0006\ufffd\ufffd\ufffdSC\ufffd̰t\u0017\ufffd\ufffd\ufffd{\ufffd\ufffd\ufffdN\ufffd\ufffdMʧ\ufffds\u0019ٟ\u000c\u000e\u0003?\ufffd\"\u001d\t\n\u0015C\ufffd\u0007\u003e\ufffd\ufffd\ufffd_\ufffd{\ufffd\ufffd\ufffd\ufffdU\u0016\ufffd\ufffdn\ufffdB\ufffdg\ufffd\ufffd\ufffd3\ufffd\ufffd\ufffd\u0018\ufffd V\ufffd\u003c\ufffd^\ufffd\u001a\ufffd\ufffd\ufffdU\ufffd.\ufffd.\ufffd\ufffdd\u001a\ufffd6\u0015\u0003\ufffdv\ufffd\ufffd\ufffdȊl*V\u0007D)\t]\ufffd~\ufffd\ufffd5\ufffdU\ufffdP\ufffdvۂ\ufffd\ufffdN;\ufffd\ufffd\ufffd?Ӕ\ufffd;;*\ufffd\ufffd^_\ufffd\ufffd;5}\ufffd\ufffd\ufffdo\u000b\ufffd\ufffdL!\ufffd@4#\u0026\ufffd\ufffd9\u0003!2\ufffd\u0005\ufffd\ufffd\ufffdv\u0007\ufffd:T\u0000\ufffd+\ufffd\ufffd\ufffd\ufffd\ufffd\u0007\ufffd\ufffdh\u0014\ufffdR\ufffd\ufffd\ufffd\ufffdȹ\ufffd\ufffd7\u000fԝ\ufffd\ufffd\tL\ufffd\ufffdG_\ufffd\u000e\ufffd\u0015Pxc)\ufffd\u000b\u0015\ufffd\ufffd\ufffd\ufffd\ufffd-Ǘ'm\ufffd\ufffdo\ufffdsj\u000b\u00128\ufffd|\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdc{\"E\u0000\u001a\ufffdhy\u0004m\u0013ޏW\ufffdo\ufffdݏW\ufffd\ufffd\ufffd\ufffd\ufffdg\ufffd-\ufffdڃ\ufffdڰ\ufffd(\ufffd\ufffdejS\ufffdu\ufffdͪ\u0014\ufffd\ufffd\u0008\ufffd\ufffdO\ufffd\u0012ͷ\u0016\ufffd\u0003*%\u0016\ufffd\ufffd(\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2127\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:38 GMT\r\nSet-Cookie: AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:03:43+05:30","url":"https://ginandjuice.shop/catalog/product?productId=1","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi","Referer":"https://ginandjuice.shop/","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/catalog/product","scheme":"https"},"raw":"GET /catalog/product?productId=1 HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi\r\nReferer: https://ginandjuice.shop/\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2461","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:43 GMT","Set-Cookie":"AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/, AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ\ufffdr\ufffd6\u0012\ufffdާ@y\ufffd(\ufffd1ES\ufffd\u001b\ufffd\"\ufffd\ufffd8Nz\ufffd\u0013k\ufffd\\\ufffdޗ\u000cDB$b\ufffd`\u0008P\ufffd2\ufffdB\ufffd\u001a\ufffdd\ufffd\u0000H\ufffd\ufffdH\ufffd\ufffd\ufffd\ufffd~\ufffd\ufffdc\ufffd\ufffd\ufffd\ufffd\u0002\ufffd\ufffdbw\ufffdѷ/o.\ufffd\ufffd1\ufffdB\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd|!\ufffd\ufffd\"\ufffd\u0003\ufffdS?2\ufffdܡ(#\ufffd\ufffd\ufffd\u0011\ufffd\ufffd\ufffd'\ufffdax\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdp\ufffd\ufffd\ufffd7\ufffd']Ї\u0002\ufffd\u00116\u0016rɈ\ufffd\u0008\ufffdm`\n\u0002\u0000\ufffd-\ufffd\ufffdd?@L$F\t\ufffd\ufffdؚS\ufffdHy\u0026-\ufffd\ufffdD\ufffdD\ufffd\ufffd\u0005\rd4\u000eȜ\ufffd\ufffd\ufffd\u000f'(\u0017$\ufffdAB\u0018\ufffd\ufffdq\n\ufffd\ufffd3\ufffdJ$2lU\u0004\ufffd(\ufffd\u0001\ufffd\ufffd\u000fH\ufffd\ufffd4\u0006\ufffd\ufffdGay#\ufffd\ufffd\ufffd\u000ea\u0007\u003c\u003e\u0000F.S\ufffd\ufffd$\ufffd\ufffd\ufffd\ufffd\ufffdؔZu\ufffd8\ts\ufffd\ufffd\u000f\ufffd\ufffd\ufffd~ڄ*\ufffdděЄ\ufffd4e\u0004]\u0005TR\ufffd\ufffdK\ufffd\ufffdIL\u0019\ufffd\ufffd$\ufffdA\ufffdK\ufffd\ufffd\ufffd\u0026\ufffd\u0011\ufffd\ufffdg\ufffd\ufffd\u001c\ufffd\u000f\ufffdF\u003c\u001d9\u0006\ufffd0\ufffdYSc4\ufffd\ufffd\u0012%\ufffd\r\ufffd\ufffd\u0001\u0003:G4\u0018[UFT\u0016\ufffdL\ufffd\ufffdZ\u0008\ufffda!Ɩ\ufffd\ufffd\u001d\u0010#=\ufffdluН\ufffd\ufffd2\ufffdy\u0017Q\ufffd\ufffd\u000f\ufffd\ufffd0:%\u0019\ufffd\ufffd-\ufffd\u003cg\t\ufffd\ufffd\rG\u000b2Ej\ufffd\ufffd\ufffdzT\u0018\ufffd\ufffd\t\tЌgH\u0012!i\u0012\ufffdF\ufffd\ufffd\ufffdH\ufffd\ufffd\u001d)\ufffdri\ufffd\ufffdT\ufffdL\ufffdk\u0005\u0018\ufffd\u0014\ufffd\ufffd\ufffd\ufffdm!\ufffd\ufffd\u001cH\u0007\ufffdM2E\ufffd$\ufffd\ufffd\ufffd\ufffd\u000f\ufffd.\u001a#wU\ufffd盵\ufffd\ufffd\ufffd\u0018\ufffd\ufffd\ufffdCS[VCiR\ufffd\u003e\ufffd蜧c\ufffd\ufffd\ufffd\ufffd\ufffdЏ\u001e\ufffd\ufffdX\u001b\ufffd\ufffd\ufffddz'\ufffd\ufffdb\ufffd\ufffd\u0008K\u003e}ܻ\u001e\ufffd\ufffd\u003eg~\ufffd\ufffd\ufffd\ufffd\ufffdi\ufffd\ufffd11\u001e\ufffd\ufffd\ufffdFe\u0004+\ufffda\u001f\ufffd$\u00158\ufffd\u0019\ufffd)\u001bԔ\rk\ufffd\ufffd,o\ufffdK\ufffd\u0004ڣb\u001f\ufffd\ufffdHFDoݷ@yX\ufffd\u001a\ufffd8[|\ufffd\ufffd\ufffdu{\ufffd\ufffd3\ufffd}\ufffd\u0007\ufffd\ufffd\ufffd*z\ufffd\ufffd\ufffd\ufffd`Ы\ufffd\ufffd\ufffd#l\ufffd\\ϩ\ufffd\ufffd\ufffd\u0013\ufffd\ufffdٌ\ufffd\u001c\ufffd\u0018\ufffd\u001b`\u001d\ufffdP\ufffd\ufffd\ufffd\nk\ufffdȧ\ufffd$\ufffda\ufffdU\ufffd\ufffd\ufffdԆ\ufffd4Ԝ\ufffd\u000b\ufffdUd\u0017(\ufffdh\ufffd\ufffd\ufffdy\ufffdQ\ufffd\u001dHF\ufffd\u001b醸\u001cz\ufffdK\t[\ufffd\u0018\ufffdK\u0012Xfe-\u0007(\ufffda\ufffd\ufffd\"Ƅ\ufffd\ufffd\ufffd\ufffd\ufffdu\ufffdH\ufffd\ufffdb\ufffd\ufffd\ufffdj\ufffd^\ufffd\ufffd?\ufffdXx\ufffdsiy7J\ufffd$ϖ\u000f$\ufffd\ufffd\ufffd\ufffd\ufffd\\\u0002e\ufffdI\ufffd\ufffdل}\ufffd牴\ufffd_Y\ufffdxi\u0017\ufffdp\ufffd\ufffdy\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdc\u0017\ufffd\u001e\ufffd/\ufffdZ\ufffd\ufffdC\ufffd\ufffdm`\ufffd\ufffdBgu\u001a+\ufffd.\ufffd[,O\ufffd\ufffd\u0007M{w\ufffd[t\ufffdN\ufffd\ufffd\ufffd\ufffd\u0026\ufffd%ָ\ufffdX\ufffdu\ufffd~\ufffdD\ufffd\ufffd\ufffd$z;\ufffd\ufffd\ufffd}\u0001a}\ufffdm\ufffd\ufffd0|\ufffd\ufffdQ|UG\ufffdiq\u0014vd\ufffdꪩ\ufffdF?\ufffd\ufffd\ufffd\ufffd\\Q|[\ufffd0d\u0004F\ufffd\u001b\ufffd\u0019\rC\ufffd*\ufffd٬\u0026\ufffd\ufffd\u0000\ufffd\u001dk\ufffdFN\ufffd\u0001\ufffdp\u0004\ufffd\u0014\ufffd8\u0016[\ufffd\ufffd\ufffd\u000c\u001e\ufffd\nY\ufffd\ufffd\ufffd1\ufffdݰSsf\ufffd\ufffd\ufffd1\u001c\ufffd\ufffd-^\u001f\ufffd֪\u001d\ufffd\ufffdv\n[\ufffd`?G\ufffd\ufffd\ufffdV\ufffdK:+\ufffd\ufffd\ufffd\u001c7utLuC\ufffdھO!\ufffd\t\ufffd,\ufffd\ufffd\ufffdl\ufffdR\ufffd\ufffdM\ufffd\u001c\ufffd\ufffd\ufffdǤ}[7{պ\u0000\ufffd\u0000\ufffd\u0001Qs\ufffdf.moZ\ufffd\ufffdn\ufffdht\ufffdV-h\u001cn\ufffd\ufffdZ\ufffdXP?8\ufffdmt\ufffd\u0016\ufffd](\u001e\ufffd\ufffd\ufffd\ufffd\ufffd@\ufffd\u003c\ufffdi\u0012\ufffd\ufffd\ufffd(v4ܻ\ufffdP\ufffd'\ufffd\ufffd\u001e\ufffd\u001aǁS\u000b*\n\ufffd$m\ufffdW߷C'\ufffd\ufffd\ufffd\ufffd\ufffd~\ufffd\ufffd\ufffd\ufffdG9;\ufffd\ufffd\ufffdm\ufffd\n\n\ufffdo\u00263\ufffd\ufffd\u0012\u001dFۘ\ufffd\ufffd\ufffd}\ufffd+\ufffdS¼\ufffd\ufffdN\ufffd\u00189\ufffd\ufffd\ufffdo\ufffd\ufffd\ufffdAA\u0013\ufffd \ufffdLjјBD\ufffd\ufffdm\ufffd`\ufffdK\ufffdR('\u0008\u001c\u00064\ufffd\ufffd\u001d\ufffd.Q\ufffdS\ufffd*\ufffd\ufffd2\ufffdIp\ufffd\ufffd\u001fJ\ufffd\u0002\ufffd\ufffd\ufffd\ufffdC\ufffd\u0005\ufffd\ufffdy\ufffdc4\ufffdr*\ufffdG\ufffd\u00029A\ufffddVY\u0012\n\ufffdj\ufffd\u0011\u0015\ufffdf\ufffdm\u0013\ufffd.xƂ\u003ez\u001fa\ufffd\u0002NDғHb\u0001\ufffd\ufffd\ufffdH\t\u0006W\ufffd\ufffd\ufffd\ufffd\u0010\ufffd\ufffd\u0015I\u0011\u000e\u0002\u0012\ufffd\u0008C}\ufffd\ufffdD\ufffdI\ufffd1\u0004q\ufffdBE3\u001aF\u0000\ufffd0yB\u0010\ufffd\ufffd:o\ufffd\u0006\ufffdxhN\u0004\u000c\u0008rD*\ufffd\ufffd\ufffd\ufffd\ufffdH\u0007s\ufffdr\ufffdB\ufffd\u0003(\ufffdy\u0018\ufffduH\u000c\u000b88?}s\ufffd\ufffd\ufffd\ufffd\u0001\ufffd\u003c\ufffd\ufffd\u0005~]|\ufffd\ufffd\ufffd\ufffdm'd\u003e\ufffd\u0017\u0010_\ufffdw\ufffd\u001f\u0011\ufffd\ufffd\u000b/`^\ufffd\ufffd\u001d\ufffd~\ufffd\ufffd\ufffd+(\ufffd\ufffd\ufffdS\u000b_\ufffd{\nEwtC\u000b\ufffd\u0004\ufffd\u0008\ufffd\u00267\ufffd\ufffd:*ވ\u0026),eF\u003e\ufffd4\u0003\u0002\ufffd\ufffd[Da'\ufffd\ufffd2\ufffd\ufffdb\ufffdB\ufffd9\ufffd,\ufffd\u0012\ufffd+\ufffd\tU\u000b\u001c\u0015f\u0011@9\ufffd5\ufffdZ/\ufffd\ufffd\"\ufffd͓\ufffd\ufffdonj\ufffd\u0018j\u0000P\u0013\ufffdQq4\ufffd\u0010\ufffd\ufffdP\ufffd\u000f\ufffdI\ufffdKjm:\ufffd.B\ufffd=\"\ufffd\ufffd\ufffd[[!\ufffd\ufffd\ufffd\ufffd4\u0017F\ufffd)\ufffd\ufffd\ufffd*\ufffdu5훬\ufffd\ufffd\ufffd\ufffdI\ufffd\ufffdl\ufffd\ufffdS\ufffd\ufffd1\ufffd]\ufffdN\ufffd\ufffdq\u001c4\ufffdr\ufffd\u0000}-\ufffd\u0001\ufffd\u0007\ufffd\ufffd\ufffd \ufffd\ufffd\ufffd\ufffd\ufffd`\ufffd\ufffd\ufffdK諔\ufffd\ufffd\ufffd\ufffdЦTl\ufffd\ufffdG\ufffd\ufffd\ufffdj\ufffd\ufffd+\ufffd.h\ufffd\u003e\u0010.\u0014Ҭ\ufffd\ufffd\ufffdz\ufffd\ufffd_\ufffd]%.LE\ufffd,}\ufffdO9N$\ufffd\ufffd\ufffdzL=NY~\ufffd-)\ufffdh\ufffd\ufffd\u0000\ufffd\ufffd\u001b\u003c\ufffd%\ufffd\ufffdG\ufffd\ufffdY\ufffd\ufffd\ufffd \ufffd\ufffdw~4\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd O-\ufffd\ufffd\ufffd \u0017\ufffdwq4\ufffd\u000f\ufffd\ufffd\ufffd\ufffd \ufffd\ufffd幧\ufffd\ufffd\ufffd\u0001\ufffd\u001e\ufffdZ\u0017h\ufffd\u001e\ufffd[\u0017\ufffd\ufffd\u001e\ufffd\\\u0017\ufffd\ufffd\u001e\ufffd]\u0017\ufffd\ufffd\u001e\ufffd\ufffd#\u000ecs\u0016\u0017f\ufffd8t\ufffd\u0007\ufffd\ufffd\ufffd\ufffd\ufffd~\ufffd\ufffd#I\ufffd\u000c\ufffd\u0007\ufffdKM\ufffd\ufffd\ufffdd\ufffdo\ufffd\\̰%A\ufffd\u0026\ufffd\ufffd[\ufffd\ufffd˪\ufffd^MW[\ufffd\ufffdЌs\u0008@\ufffdE\u0006\u0001G}\u001ei+\ufffda:4\ufffd\ufffdv\ufffd\u0019\ufffdi\ufffd\ufffdyJ\ufffd}|}A\ufffd/\u001b\u0011\r\ufffd\ufffdd\u000e\ufffdSL\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u001a\ufffd\ufffd\u001c\ufffd)QA\ufffdȁV\ufffd\u0010\ufffd\ufffd3\ufffd\ufffdh\u0015\ufffd-h@\ufffdJ\ufffd\ufffd\t\u001c\ufffd\ufffd\ufffdtĤ\u0002B\u001d\ufffd\ufffd\u0019N P\ufffdʼn\u000e\ufffdȽ\ufffdrA\ufffd0\u0012Y\u0008\ufffd\ufffd4\u0010\ufffd\ufffd\ufffdQK\u0003\ufffd\ufffdF\ufffd\ufffd@\ufffd \ufffd\ufffds\u0006\ufffd,\ufffdt:M\ufffdP3\u0006\ufffd\ufffd\ufffd7\ufffd\ufffd\ufffdp\ufffd-\u001f\ufffd\ufffd\ufffd[\ufffd\u0000/\ufffd\ufffd\ufffd\ufffd[_}\ufffd\ufffdҫ\ufffd*\u0000J\ufffdeԔr!\ufffd\ufffd\ufffd@k\rЖ\ufffd\ufffd\ufffd\ufffd ئ̸-\ufffdgʰO\"\ufffd\u0002u\ufffd}\ufffd\ufffd\ufffd\t\ufffdٶ\ufffd\ufffdt\ufffd\ufffd|\ufffd\ufffdV\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdY\ufffd\ufffdՕ{u\ufffdy\"\ufffdӋ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd~{5\ufffdo_\ufffd\ufffdWX\ufffd-\ufffdP1|\ufffdm\ufffd2햮=\ufffd\ufffd\ufffdZo\ufffd\ufffd\ufffd\ufffd'\ufffd)i\u0013r#\ufffd\ufffd\ufffd5\ufffdw\ufffd\u000c}\ufffd\u0005)m%\ufffd\ufffdgԿ+\ufffd/5\ufffdK-\ufffdc\ufffd\ufffdD\u003eQ\ufffd\ufffdپ\ufffd\ufffdI\ufffd\u0019\ufffd\u001aIG\ufffd78\ufffd\u000e\ufffdt\ufffd\ufffd%X\ufffdLG\u0008:\ufffd\ufffdq\ufffd\u001b=\ufffd\ufffd\ufffdfwE\u0018\u0016{\ufffd.\ufffd\u000b%\ufffd\ufffd\ufffd\ufffdS\ufffd\rR)\ufffd\u003c\tڳ\ufffd\u001b\ufffd\ufffd\u000e\ufffd\ufffd\u0005\ufffdj\u0013VBC\ufffd\ufffd\ufffd@\ufffd\u0019\ufffd\u000bvdcm*\ufffd\u0003PJ\u0002\u001b\ufffdߝ\ufffd\ufffdf\ufffd*T\ufffd\ufffd\u003ec\ufffd3\ufffd\u0001MS\ufffdy\ufffd\ufffd\ufffdJi\td\ufffdWN\ufffdΰ\ufffd\u0017\ufffdR\ufffd\ufffd\ufffd\n\u001bє\ufffd5#ڪ\ufffd2݂g\ufffd\r\ufffd\ufffdTVG\ufffd\ufffd4\ufffd1\ufffd\ufffdb\ufffd\ufffdN=غL%\ufffd\u000ePo\ufffd\ufffd\ufffdX\ufffd\u000b2\ufffd\ufffdH\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0017\rN\u0007C4\u0001\ufffd[\ufffd\ufffd\ufffde\ufffd\ufffd0\ufffd\ufffdn\ufffd\u001af[̴\ufffd:\ufffd\ufffdϩuH`\ufffd\ufffd\u0003ܒ\ufffd\ufffd\ufffd\u000cZ\ufffdbV\u0019K\ufffdÎ^\ufffd\ufffd|жx\ufffdV\ufffd\ufffdb\ufffd\ufffdZMm\ufffd\ufffd\u0017\ufffd\ufffd\ufffd\ufffd֍\ufffd\ufffdc\ufffdwj\ufffdL;\ufffd\ufffd\ufffd\ufffd]\ufffd\u0014N%\u001e,\ufffdo\ufffdl\ufffd\ufffd\u0017\ufffd\u001fZ\ufffdMaw,\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2461\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:43 GMT\r\nSet-Cookie: AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:03:46+05:30","url":"https://ginandjuice.shop/resources/js/stockCheck.js","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"script","Sec-Fetch-Mode":"no-cors","Sec-Fetch-Site":"same-origin","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/resources/js/stockCheck.js","scheme":"https"},"raw":"GET /resources/js/stockCheck.js HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: script\r\nSec-Fetch-Mode: no-cors\r\nSec-Fetch-Site: same-origin\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"public, max-age=3600","Content-Encoding":"gzip","Content-Length":"420","Content-Type":"application/javascript; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:46 GMT","Set-Cookie":"AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/, AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdR\ufffdn\ufffd0\u000c\ufffd\ufffd+8]*\ufffd\ufffd\ufffdm7\u0017n\ufffd\ufffd\u001d6\ufffd\ufffda\ufffd\u000f(\u00163\u000bu\ufffd\ufffd\ufffd\ufffd\u0019A\ufffd}\ufffd\ufffd\u0006uS\ufffd\u0004\u000cX\ufffd\ufffd\ufffd\u0013\ufffdlh\ufffd\u001a=\ufffd\ufffd\ufffdn:\ufffd߯\ufffd\u000f\ufffdU\ufffd\ufffd\u003c.Zl\u001e\ufffd\ufffd~\ufffd\ufffd\ufffdX{\ufffd\ufffdӷ.\u0012z\ufffd\ufffd$-\ufffd\u000cV\ufffd7\ufffd\ufffd\ufffdX\ufffd\ufffd\u00048\u001ai\ufffd\u0017\nM\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw\ufffdD\ufffd\ufffd\u001a\ufffd\rV\u00153x%i2\ufffd$=nA\ufffd_\u001b2\ufffd\ufffd(.2;\ufffd\ufffd\u001eE\ufffd5\ufffdL\ufffdH3\ufffd\ufffd\ufffd\ufffdI\ufffd\ufffd\ufffd\ufffd\ufffd\u0019l\u000c\ufffd3\ufffd\ufffdu\u0010\u0019|$\ufffd\ufffd\ufffd\u0001jЬ\u0000c\u0001\ufffd%\ufffd?\ufffdk\ufffd\ufffd:\ufffd+\ufffd\ufffd\ufffd\u000e\ufffd\nVHM\ufffd\u000f\ufffdD\ufffd0Av\ufffd\ufffd\ufffd5Gx\ufffd\ufffdb\u001f+\ufffd\ufffd\ufffd\"x\ufffd0\ufffd?\u000c\u001b\u003c\ufffd`\ufffd\ufffd\r۲\u0019Q\u0001aL\ufffd\u000cv\ufffdX\ufffd\ufffd\u0005cu\ufffd\ufffd\ufffdf\ufffd\ufffd4͔Ԣ\ufffd\ufffd\ufffd\ufffd\u0011\ufffdH\ufffd\ufffdܿ\ufffdO\ufffd\ufffdѤ\ufffd\\D\ufffd\ufffd\ufffd\u003e\ufffd\ufffdt\ufffdxg\ufffd4\u0015\ufffd'\ufffd\u000f\ufffd\u0019(H\ufffdQT\ufffd\u0011G\u0005j\u0011Rg\ufffd\u0007\u001aW\t\ufffdp\ufffd\ufffd\ufffdv\ufffd\ufffd\ufffdt\ufffd\ufffd\ufffd\ufffd_\u0018\ufffd#\ufffd`\ufffdٶ\ufffd\u001f~\ufffd\ufffdks\ufffd\u000b\ufffd\ufffdȃ\ufffd\ufffd\u0004\ufffd\ufffd\ufffd\u00078\ufffd\ufffdb\ufffd\\;\ufffd\ufffd\ufffdm\ufffd\u0000\ufffd\u000e\u001b\ufffd8\u0003\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 420\r\nCache-Control: public, max-age=3600\r\nContent-Encoding: gzip\r\nContent-Type: application/javascript; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:46 GMT\r\nSet-Cookie: AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:03:46+05:30","url":"https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"script","Sec-Fetch-Mode":"no-cors","Sec-Fetch-Site":"same-origin","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/resources/js/xmlStockCheckPayload.js","scheme":"https"},"raw":"GET /resources/js/xmlStockCheckPayload.js HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: script\r\nSec-Fetch-Mode: no-cors\r\nSec-Fetch-Site: same-origin\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"public, max-age=3600","Content-Encoding":"gzip","Content-Length":"230","Content-Type":"application/javascript; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:46 GMT","Set-Cookie":"AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/, AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffdU\ufffd͎\ufffd0\u0010\ufffd\ufffd}\ufffd\t\u00170dAo\ufffd z\ufffd\ufffd'Г\ufffd0)\ufffdn\u0003\ufffdM)(1\ufffd\ufffd\u001d]\u000e\ufffd\ufffdL~\ufffd\ufffd\ufffd)ݘ[!\ufffd\ufffd\ufffd\ufffdq\ufffd\u00045\ufffdhm\ufffd$zety\ufffd\ufffdi%D;j\ufffd7X\ufffd{\ufffdM֠\ufffd\u0015\u003c\u0004\ufffd\ufffd\ufffdAбu\ufffd\ufffde\"7\u0004q\ufffdl\ufffdu\u0002\ufffd\ufffdi\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw!\ufffd},\ufffd\ufffd4x#\ufffd\ufffd?\ufffd\u001d\ufffd7k\ufffd\ufffd8עr`Z\ufffd\ufffd\"\ufffd\ufffd\u0014\r\ufffdji^\ufffd;\ufffdC;k\ufffd\ufffdK\u0015\ufffd\t\ufffd\ufffd\u0016\ufffd\ufffd\ufffd\ufffdG\ufffd)\ufffd\ufffd\ufffd\u001c\ufffd\u001d\ufffd\u001fK\ufffd\ufffde\ufffd\u003e\ufffdO\u0011?_\ufffd\ufffd3s\ufffdG\ufffdYR\ufffd\ufffd\u000b\ufffd\ufffd\ufffd\ufffdd\u0001\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 230\r\nCache-Control: public, max-age=3600\r\nContent-Encoding: gzip\r\nContent-Type: application/javascript; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:46 GMT\r\nSet-Cookie: AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:03:46+05:30","url":"https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"none","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/resources/js/xmlStockCheckPayload.js","scheme":"https"},"raw":"GET /resources/js/xmlStockCheckPayload.js HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: none\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"public, max-age=3600","Content-Encoding":"gzip","Content-Length":"230","Content-Type":"application/javascript; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:47 GMT","Set-Cookie":"AWSALB=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/, AWSALBCORS=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffdU\ufffd͎\ufffd0\u0010\ufffd\ufffd}\ufffd\t\u00170dAo\ufffd z\ufffd\ufffd'Г\ufffd0)\ufffdn\u0003\ufffdM)(1\ufffd\ufffd\u001d]\u000e\ufffd\ufffdL~\ufffd\ufffd\ufffd)ݘ[!\ufffd\ufffd\ufffd\ufffdq\ufffd\u00045\ufffdhm\ufffd$zety\ufffd\ufffdi%D;j\ufffd7X\ufffd{\ufffdM֠\ufffd\u0015\u003c\u0004\ufffd\ufffd\ufffdAбu\ufffd\ufffde\"7\u0004q\ufffdl\ufffdu\u0002\ufffd\ufffdi\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw!\ufffd},\ufffd\ufffd4x#\ufffd\ufffd?\ufffd\u001d\ufffd7k\ufffd\ufffd8עr`Z\ufffd\ufffd\"\ufffd\ufffd\u0014\r\ufffdji^\ufffd;\ufffdC;k\ufffd\ufffdK\u0015\ufffd\t\ufffd\ufffd\u0016\ufffd\ufffd\ufffd\ufffdG\ufffd)\ufffd\ufffd\ufffd\u001c\ufffd\u001d\ufffd\u001fK\ufffd\ufffde\ufffd\u003e\ufffdO\u0011?_\ufffd\ufffd3s\ufffdG\ufffdYR\ufffd\ufffd\u000b\ufffd\ufffd\ufffd\ufffdd\u0001\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 230\r\nCache-Control: public, max-age=3600\r\nContent-Encoding: gzip\r\nContent-Type: application/javascript; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:47 GMT\r\nSet-Cookie: AWSALB=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:03:46+05:30","url":"https://ginandjuice.shop/resources/js/stockCheck.js","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"none","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/resources/js/stockCheck.js","scheme":"https"},"raw":"GET /resources/js/stockCheck.js HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: none\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"public, max-age=3600","Content-Encoding":"gzip","Content-Length":"420","Content-Type":"application/javascript; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:47 GMT","Set-Cookie":"AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/, AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdR\ufffdn\ufffd0\u000c\ufffd\ufffd+8]*\ufffd\ufffd\ufffdm7\u0017n\ufffd\ufffd\u001d6\ufffd\ufffda\ufffd\u000f(\u00163\u000bu\ufffd\ufffd\ufffd\ufffd\u0019A\ufffd}\ufffd\ufffd\u0006uS\ufffd\u0004\u000cX\ufffd\ufffd\ufffd\u0013\ufffdlh\ufffd\u001a=\ufffd\ufffd\ufffdn:\ufffd߯\ufffd\u000f\ufffdU\ufffd\ufffd\u003c.Zl\u001e\ufffd\ufffd~\ufffd\ufffd\ufffdX{\ufffd\ufffdӷ.\u0012z\ufffd\ufffd$-\ufffd\u000cV\ufffd7\ufffd\ufffd\ufffdX\ufffd\ufffd\u00048\u001ai\ufffd\u0017\nM\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw\ufffdD\ufffd\ufffd\u001a\ufffd\rV\u00153x%i2\ufffd$=nA\ufffd_\u001b2\ufffd\ufffd(.2;\ufffd\ufffd\u001eE\ufffd5\ufffdL\ufffdH3\ufffd\ufffd\ufffd\ufffdI\ufffd\ufffd\ufffd\ufffd\ufffd\u0019l\u000c\ufffd3\ufffd\ufffdu\u0010\u0019|$\ufffd\ufffd\ufffd\u0001jЬ\u0000c\u0001\ufffd%\ufffd?\ufffdk\ufffd\ufffd:\ufffd+\ufffd\ufffd\ufffd\u000e\ufffd\nVHM\ufffd\u000f\ufffdD\ufffd0Av\ufffd\ufffd\ufffd5Gx\ufffd\ufffdb\u001f+\ufffd\ufffd\ufffd\"x\ufffd0\ufffd?\u000c\u001b\u003c\ufffd`\ufffd\ufffd\r۲\u0019Q\u0001aL\ufffd\u000cv\ufffdX\ufffd\ufffd\u0005cu\ufffd\ufffd\ufffdf\ufffd\ufffd4͔Ԣ\ufffd\ufffd\ufffd\ufffd\u0011\ufffdH\ufffd\ufffdܿ\ufffdO\ufffd\ufffdѤ\ufffd\\D\ufffd\ufffd\ufffd\u003e\ufffd\ufffdt\ufffdxg\ufffd4\u0015\ufffd'\ufffd\u000f\ufffd\u0019(H\ufffdQT\ufffd\u0011G\u0005j\u0011Rg\ufffd\u0007\u001aW\t\ufffdp\ufffd\ufffd\ufffdv\ufffd\ufffd\ufffdt\ufffd\ufffd\ufffd\ufffd_\u0018\ufffd#\ufffd`\ufffdٶ\ufffd\u001f~\ufffd\ufffdks\ufffd\u000b\ufffd\ufffdȃ\ufffd\ufffd\u0004\ufffd\ufffd\ufffd\u00078\ufffd\ufffdb\ufffd\\;\ufffd\ufffd\ufffdm\ufffd\u0000\ufffd\u000e\u001b\ufffd8\u0003\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 420\r\nCache-Control: public, max-age=3600\r\nContent-Encoding: gzip\r\nContent-Type: application/javascript; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:47 GMT\r\nSet-Cookie: AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:03:48+05:30","url":"https://ginandjuice.shop/catalog/product/stock","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Content-Length":"107","Content-Type":"application/xml","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"same-origin","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/catalog/product/stock","scheme":"https"},"body":"\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\u003cstockCheck\u003e\u003cproductId\u003e1\u003c/productId\u003e\u003cstoreId\u003e1\u003c/storeId\u003e\u003c/stockCheck\u003e","raw":"POST /catalog/product/stock HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nContent-Length: 107\r\nContent-Type: application/xml\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: same-origin\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"23","Content-Type":"text/plain; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:49 GMT","Set-Cookie":"AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/, AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd32\ufffd\u0000\u0000\u0003\u0004\ufffd\u001d\u0003\u0000\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 23\r\nContent-Encoding: gzip\r\nContent-Type: text/plain; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:49 GMT\r\nSet-Cookie: AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:03:50+05:30","url":"https://ginandjuice.shop/catalog/cart","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Content-Length":"36","Content-Type":"application/x-www-form-urlencoded","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/catalog/cart","scheme":"https"},"body":"productId=1\u0026redir=PRODUCT\u0026quantity=1","raw":"POST /catalog/cart HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nContent-Length: 36\r\nContent-Type: application/x-www-form-urlencoded\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"0","Date":"Thu, 07 Sep 2023 15:33:50 GMT","Location":"/catalog/product?productId=1","Set-Cookie":"AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/, AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"raw":"HTTP/1.1 302 Found\r\nConnection: close\r\nContent-Length: 0\r\nContent-Encoding: gzip\r\nDate: Thu, 07 Sep 2023 15:33:50 GMT\r\nLocation: /catalog/product?productId=1\r\nSet-Cookie: AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:03:51+05:30","url":"https://ginandjuice.shop/catalog/product?productId=1","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/catalog/product","scheme":"https"},"raw":"GET /catalog/product?productId=1 HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2459","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:51 GMT","Set-Cookie":"AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/, AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ\ufffdr\ufffd6\u0012\ufffdާ@y\ufffd(\ufffd1ES\ufffd\ufffd\ufffd\"\ufffd\ufffd\ufffdiz\ufffdc\ufffdu\ufffd\ufffdݗ\u000cDB\u0014b\ufffd`\u0008P\ufffd2\ufffdB\ufffd\u001a\ufffdd\ufffd\u0000H\ufffd\ufffdH\ufffd2\ufffd\ufffd~\ufffd\ufffdc\ufffd\ufffd\ufffd\ufffd\u0002\ufffd\ufffdbw\ufffdѷ\u0017\ufffd\ufffd\ufffd\ufffdu\ufffd\u001a\ufffddļoF\ufffd\u000b\ufffdg4#80?\ufffd#\ufffd\ufffd\u001d\ufffd\ufffdd:vR\"x\ufffd\ufffdD8\u000cOT3\ufffd:\ufffd\u0010\ufffd\ufffdq\ufffd\ufffd\ufffd\ufffd\u000b\ufffdP\ufffdR\ufffd\ufffdB.\u0019\u00113Bd\u0013\ufffd\ufffd\u0000@q\u000b0\u0011\ufffd\u000f\u0010\u0011\ufffdQ\ufffd#2\ufffd\ufffd\ufffd,\u0012\ufffdJ\u000b\ufffd\u003c\ufffd$\ufffdckA\u00039\u001b\u0007dN}b\ufffd\ufffd#\ufffd\t\ufffd\ufffd !\ufffd\ufffd\ufffd8\ufffdV\tM\ufffd)M$\u0012\ufffd?\ufffdJ\u0002}\u0012\ufffd\ufffd}\ufffd\u0007$\ufffdx\u0012\u0001x\ufffd\ufffd\ufffd\ufffd\ufffdcz\ufffd\ufffd\ufffd\u0003\u001e\u001d\u0000#\ufffd\t\ufffdL\ufffd{\ufffd|\ufffdslJ\ufffd*t\u001c\ufffd\u0019\ufffd\ufffdG\ufffd~n?\ufffdC\ufffdT2\ufffd\ufffdИ\ufffd$a\u0004\ufffd\u000e\ufffd\ufffd\u003cF\ufffdܿ\ufffd\ufffd2d\ufffd\ufffd\ufffd\u0007\ufffd/\ufffd\ufffd\u001b\u001a\ufffd'8J^\ufffd_2X?t;\ufffd\ufffd\ufffd1\u0010\ufffd\u0019Κ\u001a\ufffd\t\u000f\ufffd(\u000em\u0000.\r\u0018\ufffd9\ufffd\ufffd\ufffd*3\ufffd\ufffd\ufffdf\ufffd\ufffd\ufffdB\ufffd\u000c\u000b1\ufffd\u000c\ufffd\ufffd\ufffd\u0018\ufffd\ufffdf\ufffd\ufffd\ufffd\ufffd얩\ufffd\ufffd\u0019\u0015\u0008\ufffd0\n\u0008\ufffd\u0013\ufffdbI\ufffd\u0012\ufffd3\u0016\ufffdo\ufffdp\ufffd \u0013\ufffd\u0026O}\ufffdG\ufffdqh\u0018\ufffd\u0000My\ufffd$\u0011\ufffdơj\ufffd$\ufffd\ufffd\ufffdeё2*\ufffd\ufffdHM\u0005\ufffdD\ufffdR\ufffd\ufffdH\ufffdj*\ufffd\ufffd\u0016\ufffdqāt\ufffd\ufffd$U\ufffd\ufffd\u0003\ufffd\ufffd\ufffdx\ufffd\ufffd1rW\ufffd|\ufffdY;ب\ufffd\ufffd\ufffd\\;4\ufffdE5\ufffd\ufffd\ufffd\ufffdY2\ufffd\ufffd\u0014=-\u000f\ufffd\ufffd\t*\ufffd\ufffd\ufffd8\ufffd|\u003cy\ufffd\u0018\ufffdWk\ufffd\ufffd䓧\ufffd\ufffd\ufffd\ufffd\ufffdɛS\ufffdG?Q\ufffd\ufffd\u003e\t\ufffd0\ufffd/\ufffd\ufffdը\ufffd`%3\ufffd\u0003\ufffd\ufffd\u0004\ufffd\ufffdD;e\ufffd\ufffd\ufffdaEى\ufffd\ufffdd\u0012-\ufffd\ufffd(\ufffd\u0007$9\ufffd3\ufffd\ufffd\ufffd[\ufffd\u003c,\u0005]\ufffd-\ufffd@Cú\ufffd\\\ufffd\u0019ھ\ufffd\u0003\ufffd*`\u0015\ufffdMSer0\ufffdUZ\ufffd\ufffd\u00116V\ufffd\ufffdT\ufffdT\ufffd\tm\ufffdl\ufffdC\u000ez\u000c\ufffd5\ufffd\u000e\ufffd\ufffdXCi\ufffd\ufffd\u001b\ufffd\ufffd}b\\3̪E\ufffd\njC[\u001ajNع\ufffd*\ufffd\u000b\ufffd\ufffd4\ufffd\ufffd\ufffd\u003cU(\ufffd\u000e$\ufffd͍tC\\\u000c=ɤ\ufffd-R\u000c\ufffd%\t,\ufffd\ufffd\ufffd\u0003\u0014Ű`@\u0011c\ufffdD\ufffd\ufffdl\ufffd:m$x\ufffd\ufffd+\ufffd\u0026Z\ufffdW\ufffd\ufffdO%\u0016\ufffd\ufffdLZR(\ufffd\ufffd\ufffd#\t7r2֙K\ufffd\ufffd\u003c\u000e\ufffd2\ufffd\ufffd\ufffd\ufffd,\ufffd6\ufffdK\ufffd\u0012-\ufffd\ufffd\u001c\ufffdQ1\u000f\ufffd\u0011\u0018\ufffd\ufffdr}\ufffd\u0002\ufffdC\ufffd\u0005]\ufffd\ufffd}h\ufffd\ufffd\rL\ufffd_\ufffd\ufffdNc\ufffd\ufffdfy\ufffd\ufffdi\ufffd\ufffd\ufffdi\ufffdN\ufffd\u000e\ufffd\ufffd_\ufffd\u0010Ѹ\ufffd\ufffd\u001a\ufffd\u0015k\ufffd\ufffd\ufffd\ufffd\ufffd(\ufffd\ufffd\ufffdDo\ufffdv{\ufffd\u0007\u0010\ufffd\ufffd\ufffd\u0016[s\ufffd\ufffd\ufffd\u001a\ufffdWu\u0004\ufffd\ufffdQؒ\ufffd\ufffd\ufffd\ufffd\ufffd\u001a\ufffdp\ufffdrI\ufffdm\ufffdÐ\u0011\u0018\ro\ufffd\ufffd4\u000c\ufffd\ufffdhf\ufffd\ufffdL\ufffd\u0003\ufffd\ufffd\ufffd*\ufffd\ufffd\ufffd\u0003\ufffd\ufffd\u0008\ufffd(\ufffdp,\ufffdZ\ufffd\ufffd\u0019\u003c\u0016\u0015\ufffd\ufffd\ufffd\ufffd\"\u0002\ufffda'\ufffd\ufffdj\ufffd\ufffd#8\ufffdW'y\ufffd[\ufffd\u003e\ufffd\ufffdU;\ufffd\ufffd\ufffd\u0004\ufffd\ufffd\ufffd~\ufffd\ufffd\ufffd]\ufffd\u003e\ufffdt\ufffd;ǹ9\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdڵ}\ufffd@\ufffd\u0013\ufffdi\u0016M\ufffd\ufffdp\ufffd\n\ufffd\ufffdd9\ufffd\ufffd3\ufffdH\ufffd\ufffdn\ufffd\ufffdt\u0001\ufffd\u0001\ufffd\u0003\ufffd\ufffd\ufffd\ufffd\\\ufffd\ufffd\ufffd\ufffd\ufffdQ\ufffd\ufffd\ufffdZ\ufffd(\ufffd³\ufffd\ufffd\u0016\ufffd\ufffd~p\ufffd\ufffd\ufffd\ufffd-\n\ufffd\ufffd?\n')\ufffd\ufffd\ufffd\ufffdy\ufffd\ufffd8\ufffd'W\ufffd\ufffd\ufffdR\ufffd\ufffdp\ufffd\ufffdB\ufffd\ufffdp\ufffdz\ufffdr\u001c\u0007N-\ufffd(Ē\ufffd\ufffd_u\ufffd\u0016\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdq\u000b;[\u001d\ufffd\ufffd\ufffdS\ufffd\ufffd\ufffd*ȭ\ufffd\ufffd̰\ufffdJ\ufffd\u0018mc\ufffd\ufffd#\ufffd\ufffd\ufffd\u000cO\u0008\ufffd.֝\ufffd1rLYs\ufffdĻ\ufffd\ufffd\ufffdƘA\ufffd\ufffd\u0011\ufffd\u0011\ufffd\ufffd\u0000\ufffd\ufffd\u000c\ufffd\u0008\ufffd\\\ufffdP\ufffd\u00108\u000ch\ufffd\ufffd;4Y\ufffd\ufffd'*U\u0002\ufffde\ufffd\ufffd\ufffd\u0008\ufffd?\u0014\ufffd\u00054'\u0011\ufffd\ufffd\ufffd\u000b*g\ufffd)\ufffd\ufffd4ͨD\ufffdT\n\ufffd\u0008\ufffd\ufffdYeI(\ufffd\ufffdYJT(\ufffd\u0002\ufffdM|\ufffd\ufffd)\u000b\ufffd\ufffd\ufffd\u000cK\u0014p\"\ufffd\ufffdD\u0012\u000b\ufffdg'DJ0\ufffdz\u0014%\ufffd\ufffd\u0000\r\ufffdH\ufffdp\u0010\ufffd\ufffdG\u0018\ufffd\ufffd\u0017\ufffd#\ufffd@z\ufffd!\ufffd\ufffd\u0015*\ufffd\ufffdp\u00060\n\ufffd\ufffd\u0004\ufffd\ufffd\ufffd\ufffd\u0016j@\ufffd\ufffd\ufffdD\ufffd\ufffd \ufffdL\ufffd\ufffdTT\u001c\u001a\ufffd`.R.Q\ufffdy\u0000\ufffd\u003c\u000bg}\u001d\u0012\ufffd\u0002\u000eN\ufffd\ufffd]\ufffd\ufffd w\ufffdn\ufffd\ufffd\ufffd¯\u0017ߡ\ufffdW\ufffd\ufffd̝y\u0001\ufffd\ufffdg\ufffd3\ufffdߵ\ufffd\u0005\ufffd+2\ufffd#\ufffd\ufffd\\u\ufffd\t\ufffd,\ufffd\ufffd\ufffd\ufffd\ufffd\\\ufffd\u001d\ufffd\ufffdB\u0011\ufffd)B\ufffd\ufffd\ufffd\ufffd\ufffd-\u0015oD\ufffd\u0004\ufffd2%\ufffd3\ufffd\u0002\u0001L\ufffdmFa'\ufffd\ufffd2\ufffd\ufffd|\ufffdB\ufffd9\ufffd,\ufffd\u0012\ufffd-\ufffd\tUs\u001c\u0015f\u0011@9\ufffd5\ufffdZ/\ufffd\ufffd\"\ufffd\ufffd〃onj\u001e\u000c5\u0000\ufffd\u001b\ufffdR\ufffd\u0019i\u0008H\ufffd(\ufffd\u0007ʤ\ufffd%\ufffd6-[硿\ufffd\u001e\ufffdM@ݭ\ufffd\ufffd\ufffd\ufffdLA\ufffd\u000b#\ufffd\u0014\ufffdq]\u0015\ufffdښ\ufffdMV\ufffdFDƤ\ufffd\ufffd6\ufffd\ufffd)\ufffd\ufffd\ufffdݮpo\ufffd\ufffdq\u001c\ufffd\ufffdr\u001f\ufffd\ufffd\u0016\ufffd\u0000̃\ufffd\\G\u0010\ufffdf\ufffdWs\ufffdz\ufffd\ufffd9\ufffdUJ^\ufffd\ufffdFhS(\ufffd\ufffd\ufffd\u000ejm\ufffd\ufffd(\ufffdJ\ufffds\u001a\ufffd\ufffd\ufffd\u000b\ufffd4\ufffd1o~\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdĹ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd9ñ\ufffdr\ufffd[\ufffd\ufffd\ufffd)ʻؒ\"\ufffd\ufffdB\ufffd\ufffd\ufffd\ufffd\u001b\u003c\ufffd%\ufffd\ufffd\ufffdAN,\ufffd\ufffd3ȩ\ufffd\ufffdv\u0006\ufffd\ufffd\ufffd\ufffd\ufffd\u000c\ufffd\ufffd\ufffd\ufffdw\u0006yay/:\ufffd\ufffd`y?t\u0006q\ufffd-\ufffd=\ufffd\u000e\u0003\u0007\ufffd\ufffd.\ufffd\ufffd\ufffd\ufffd[\u0017\ufffd\ufffdvg\ufffd\u000b\ufffdu\ufffds\ufffd\u0005\ufffd\ufffd\u0007\ufffd\ufffd\ufffdal\ufffd\ufffd\ufffdl\ufffd\ufffd\ufffdY\u0010(\ufffdX\ufffd\ufffd\ufffd=\ufffd\u000f\u003e\ufffd\ufffdͰ}\ufffd\ufffdT\ufffdj\ufffdL\ufffd\ufffd\u0001\ufffd\ufffd\u000c\u001b\u0012dMB\ufffd\ufffd婼\ufffd\ufffd\ufffdUw\ufffdUJ\rM9\ufffd\u0000\ufffd^\ufffd\u0010pT瑶R\u0018\ufffdC]\u001eh\u0017\ufffd\ufffd\ufffdv\ufffd꧴\ufffd\ufffd\ufffd\u0017\ufffd\ufffd\ufffd\u0011\ufffd\ufffdwE\ufffd\u00109ET\ufffd+^\ufffdn\ufffd\ufffd{ʑ\ufffd\u0010\u0015č\u001chU\u000f\ufffdx\ufffdp\u0008\ufffdV\ufffdق\u0006\u0004\ufffd$\\\u0016\ufffd1\ufffd\ufffdTGL* \ufffd\u0001\u001f\ufffd\ufffd\u0018\u0002e_\u001c\ufffd0\ufffd\ufffd\ufffd,\u0013t\u000e#\ufffd\ufffd\ufffd\u0008L\u00031\ufffd\ufffd\u001e\ufffd4\ufffd\u000ej\ufffd\ufffd\n\u0004\u000b\u0002O\u003eg\u0010\ufffd\ufffdT\ufffdӄ\t5#\ufffd\ufffd\ufffd{ë\ufffd\u000f\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd^\ufffd\u0005xq\ufffd\u0016^\ufffd\ufffd\u0012\ufffd\ufffdk\ufffd\ufffd^\u0005T\u0001P\ufffd.\ufffd\ufffd\ufffd\u000b\ufffd\u0017\ufffd\u0004Zk\ufffd\ufffd\ufffdM\ufffd\u0004\ufffd6e\ufffdm1?\u0013\ufffd}2\ufffd,P\u0017߯u\u00118\ufffd0ۦ\ufffd\ufffd6њ/\ufffd\ufffd*P{\ufffdk\ufffd\ufffd%\ufffdE\ufffd\ufffdW\ufffd\ufffd\ufffd\ufffd*\ufffd;\ufffd\ufffdno\ufffdr\u001d\\}Β\ufffd\ufffdrK\ufffde\u0010J\ufffdϻ-V\ufffd\ufffd\ufffd5'\ufffd\ufffdZ\ufffd\r\ufffdy\ufffd\ufffd\ufffd6%MBn\ufffd~u\ufffdz\ufffdn\ufffd\ufffdϸ \ufffd\ufffd\u0004\ufffd\ufffd\ufffd\ufffdwy\ufffd\ufffdƾ\ufffd\"=\u0005m\ufffd\ufffd3\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd4\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0006\ufffd߁\ufffdN\ufffd\ufffd\u0004K\ufffd\ufffd\u0008A\ufffd\ufffd:\ufffdU\ufffd'پ\ufffd\ufffd\ufffd\u0008\ufffd|o\ufffd\ufffdz\ufffd\ufffd68\u0010w\ufffdo\ufffdA*%\ufffd\ufffdAsvs\u00036߁\u0002:\ufffd]m\ufffdJh\ufffd\ufffd\ufffd\u0018(5\ufffd?`G6֦\ufffd:\u0000\ufffd$\ufffd\ufffd\ufffd\ufffdY[kV\ufffdB\ufffd\ufffd\ufffd3\ufffd;#\u001d\ufffd4\ufffdΒ\ufffd-UJK 3\ufffdr*t\ufffdM\ufffd\ufffd\ufffd\ufffd\ufffduT؈\u0026\u0004\ufffd\u0019\ufffdVE\ufffd\ufffd\u0016\u003cUo\ufffd5\ufffd\ufffdZ\nդ\ufffd\ufffdQ\ufffd\u0006K\ufffdu\ufffd\ufffd֥*\ufffdw\ufffdz\ufffd\ufffd\ufffd\ufffdB_\ufffd\ufffd7DZ-\ufffdN\ufffd\ufffd\ufffd\ufffdhp\u003c\u0018\ufffd\u001b@\ufffd5\u0008\ufffdR\u0006\ufffd\u00163;\ufffdf\ufffdf\ufffd\ufffdLk\ufffd\ufffd\ufffd\ufffd\ufffdJ\ufffd\u0004f\ufffd8\ufffd-\ufffdZ\ufffdg\ufffdp\u0017\ufffd\ufffdXr\u001fv\ufffd\ufffd\ufffd僦\ufffd\ufffd뵚\ufffd\u0017\ufffd\ufffd\ufffdj*k\u000f\ufffd\ufffd\ufffd\ufffd\u0015\ufffdn\ufffd\ufffd\u001ek\ufffdS\ufffdd\ufffdI5/\ufffdp*\ufffd`\ufffd}\u0003g\ufffd~\ufffd\ufffd\ufffdf\u0001L\\w,\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2459\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:51 GMT\r\nSet-Cookie: AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:03:58+05:30","url":"https://ginandjuice.shop/catalog/subscribe","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Content-Length":"69","Content-Type":"application/json;charset=UTF-8","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"same-origin","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/catalog/subscribe","scheme":"https"},"body":"{\"email\":\"eqeq@gfmail.com\",\"csrf\":\"GQrKlppDdKQale2DNpk4mASSUzOdNqup\"}","raw":"POST /catalog/subscribe HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nContent-Length: 69\r\nContent-Type: application/json;charset=UTF-8\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: same-origin\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"68","Content-Type":"application/json; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:58 GMT","Set-Cookie":"AWSALB=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/, AWSALBCORS=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdVJ\ufffd/-\ufffd\ufffdS\ufffdR\ufffd\ufffd\ufffdS\ufffd\n\ufffdtv5\ufffd0\u0008P\ufffdQJ\ufffdM\ufffd\ufffd\u0001\ufffd\ufffd\u0016\ufffd\u0016:\ufffd\ufffd\ufffdxz\ufffd\ufffd\ufffdJ\ufffd\u0000-/{\ufffd4\u0000\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 68\r\nContent-Encoding: gzip\r\nContent-Type: application/json; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:58 GMT\r\nSet-Cookie: AWSALB=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:03+05:30","url":"https://ginandjuice.shop/blog","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR; AWSALBCORS=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/blog","scheme":"https"},"raw":"GET /blog HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR; AWSALBCORS=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2646","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:04 GMT","Set-Cookie":"AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/, AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ\ufffdn\ufffd8\u0016\ufffd\ufffd\ufffd`\ufffdئ\u0005b{\ufffd\ufffd\ufffd\u0005j{п\ufffd\ufffdh\ufffd\ufffd\ufffdآ{S\ufffd\u0012m1\ufffdH\ufffdH\ufffd\ufffdݾ\ufffd\u003e¾\ufffd\u003e\ufffd\u003e\ufffd~\ufffd\ufffd\u001d\ufffd_i\u001b\ufffd\u0017S\u0014\ufffdD\u001e\u001e\u001e\ufffd|\u003c\ufffd\ufffdދw\ufffd?|z\ufffd\ufffd\ufffd.S\ufffd\ufffd\u0006\ufffd\ufffd\ufffd\ufffd \u0015\u003c\t\ufffd\ufffdUI}\ufffd\ufffdBL\ufffd\ufffdBXS\u0016\ufffd\ufffd=\ufffd\ufffdD\u0026\ufffd^lm\ufffd\ufffd\\_\ufffd\ufffd}C\u0017\r\ufffd\u0010jh]\ufffd\ufffdM\ufffdp\ufffd\ufffd\u0011\u000b0\ufffdϔ\ufffd~\ufffd\ufffdKH\ufffd\ufffd\ufffd\u000c2\ufffd8\ufffd\u003c\u0013\ufffdh\u0026\ufffd\u003c7\ufffd\ufffdXl\ufffd\u0013\ufffd\r\ufffd\ufffdL\\:L\ufffdLƢ\ufffd_\ufffdYiE\ufffd\ufffd\u00021\ufffd\u0012Cm\ufffd\u00067\u001b\u00172w\ufffd\u0016\ufffd0j\u0008te\ufffd\ufffdc\ufffd\u0005'\ufffdL\ufffd\ufffdy\ufffd\ufffdF\ufffdA/\ufffdhϢ\ufffd\ufffd\ufffd\u0016l\\\ufffdceN|q\ufffd+\u003e\ufffd\ufffd5\ufffdƝ\ufffdi\ufffdx\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd.\ufffdN:%F\ufffd\u0016\ufffda\ufffd\ufffdf\ufffdy\ufffd?a\ufffdK\ufffd\u000f\ufffdLM\u003e\ufffd\u0005\ufffd\u0000\ufffd\ufffd\rr\u0006c\ufffdTLO;\u003c\ufffd\u001b\u000c\u00139c2\u0019FM\ufffd464,E\ufffdN\u001a\ufffdbŭ\u001dF\u0001f\ufffdD\u0004\ufffdг6\ufffd\u000f\ufffd7\ufffd\ufffd߇TZ\ufffd\ufffd\ufffd%Bɱ(\ufffd\u0013\ufffdb\ufffdRi\u003cC\ufffdl.\ufffd\u000c2*\u0019s?+\ufffd\ufffdS-\u001261\u0005s\ufffd:\ufffd\ufffdDt_\ufffdm\ufffdd1P*\ufffd\ufffd\ufffdDKA\ufffd\ufffdn\u0015``s\ufffd\\\n\ufffd7bFg\u0006\ufffd\ufffd2EA\ufffdӉ\ufffdw?\ufffd\ufffdgC\ufffd_v\ufffd\ufffdj\ufffd\ufffdJo\ufffd\ufffdf\ufffdi\ufffd]t\ufffdU7\ufffd\ufffdV\u0006\ufffd\ufffd0\ufffd\u0013\ufffd\ufffd9\ufffd\ufffd\ufffd\ufffd9\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdC\ufffdL\ufffd[CƝ\u0019?8zs\ufffd\ufffd\ufffd\ufffd\ufffd#\u0015g\ufffdI\ufffd\ufffdK\ufffd\ufffdR*\ufffd\u000f\ufffdG\u000f\ufffd\ufffd*\ufffdIf\ufffdA8\ufffd`\ufffd\ufffd6\ufffdN\ufffd\ufffd\ufffdni;\ufffdF\ufffdK\ufffd*\ufffd\ufffd\ufffdz`\ufffd0\ufffd\n\ufffd\ufffd{\ufffd4\ufffd\u000b\\zkx\u0001a@\ufffd^,\u001e\u0005\ufffd\u003e\ufffd\u0013\u001dmaK\ufffd\u000e\ufffddR\ufffd\u0004\ufffdv\\\u000ex\ufffdbG\ufffd-l\ufffd\ufffd\ufffdup\u0010\r\ufffd)\ufffdw\ufffd\ufffd\ufffd\u001d\u001d7\ufffd\ufffd\ufffd\ufffd\u001c\ufffdϏ\ufffd|\ufffd4K\ufffdR-\ufffd\rZ9\ufffd\ufffd\ufffd\ufffd\u0007\ufffd\ufffdnY^Ȍ\u0017Ux\ufffdrp7X*y\ufffd\ufffd\u0013\ufffd\ufffd\ufffd\ufffd\ufffd9\u0018\ufffd\ufffd\ufffdQ\u000f\ufffd\ufffd\ufffd' \ufffd0I\u0019;\ufffdsSV\ufffd\ufffd\ufffdL\ufffd\ufffd\ufffd1\ufffd\ufffdD\ufffd\u0014s\ufffde$\ufffd\ufffd#ȷ\u0014\ufffd\ufffdM\ufffd\ufffd\ufffd;:P\ufffd\u0014\ufffdw\u0012n\ufffd+\ufffd7c\t\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdؔ\ufffdud\ufffdؕ\ufffd\ufffd\ufffd\ufffdp\ufffdv6\u001d\r`\ufffd\u0016\ufffd7n\u0015P\ufffd\ufffd\u000f\ufffd.軠\ufffd\ufffd\nO\ufffd_\u000c\u0026oK|\ufffdlo\ufffd=\ufffd\u0008o\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd7\ufffd\u0002\ufffdn-\ufffd\ufffd\ufffd\n\ufffdw+\ufffdE\ufffd\ufffd\ufffd;\u0012\ufffd0\ufffd\ufffd\ufffd\ufffd\n\ufffdƼXCkm\u0001{\ufffdCx%\u0017د]aK\ufffd\ufffdP\u000f\ufffd\u001b\ufffd\ufffd\ufffdm\ufffd%7\u000e~Ǚ\ufffdT\t\ufffd\ufffdWZ\u000b9\ufffdR\ufffd\u0018V\ufffd\\\ufffd\ufffd\u0017\ufffd\ufffd\ufffdVi\ufffd\ufffd\ufffd\u0000w\ufffd\ufffd-\ufffd[\u0002\ufffd5*\ufffd\ufffd\u0011\ufffdPJ\u0012\ufffd\ufffd\ufffdp8\ufffd\ufffd^:\ufffdm\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdt\u0008\ufffd;9\ufffd\ufffd\ufffdd\u000e\ufffd\ufffd]n\ufffdqrR\ufffdõ\u0005\ufffd5\ufffd\u0017\ufffdw\ufffd\ufffd\ufffd\u000e\u000e\u000f;\ufffd\ufffd\ufffd\ufffd\ufffdh5?ID\ufffd\u000b\ufffd!39jd\u0026{\u0006X\ufffd\ufffd8\ufffd\ufffd\u0002N\ufffdFm\ufffdze_\ufffd\ufffd=`@:\ufffd\ufffd\ufffdА\ufffdw\ufffd=\ufffd\\25\ufffd\ufffd\ufffd\ufffd\u000f\u0007`\u0026u^ֹ\u0019\ufffdf,W\u003c\u0016\ufffdQع\ufffdѥ\ufffd\ufffdǬĶ\ufffd\ufffd\u001e\ufffd\u0004u]\ufffd\u0026\ufffdT\u0026\ufffdЁn\ufffd\ufffdk6\ufffd\ufffd\u0014uX\ufffd;\ufffd\u0019\u0007uL\ufffd9\ufffdr\ufffdIW\ufffdB\ufffd\u0018\u0005\ufffd\u0006\ufffd\ufffdu\ufffd\t\ufffd]\ufffd\u0005\ufffd\ufffd\u0011\ufffd\ufffd{\r\ufffd$7ܺu{doB\ufffd\ufffd\ufffdf?=o\u0006[=\ufffd\ufffd\ufffd\ufffd\ufffd'\ufffdSX\u0012\ufffdM\ufffd\ufffd\ufffd[\ufffd^\ufffd\ufffdohm\ufffdQ\ufffd*\ufffdF\ufffd\ufffd\ufffd =\u0019=e\ufffdsYT\ufffd\u0005G\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdaI\ufffd$\ufffd\u001e{\ufffd\u000be,KE!\u0018\ufffd\ufffd\ufffdv\ufffd9\ufffds\ufffd|\n#\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdS\ufffdX\ufffd\u003cUPj\ufffdD\ufffd\ufffdB\u0001\ufffd\ufffdO\u0006\n\ufffd\ufffd\ufffd\ufffd.\ufffd\u003e\ufffd|,\ufffd\n\ufffd\ufffdW\u000bvr\ufffd:F9%\ufffd\ufffd\ufffd1Ȕ\ufffdr\ufffd\ufffd\ufffd;a\ufffd\ufffd:\ufffd\ufffdX\ufffdI\ufffdLX\u0004\ufffd]vi\ufffd\u000fx\ufffd\u0018\u0019t\ufffd\u001a-X̕\u0002B7\ufffd\ufffd\ufffdm_\ufffd}a|l\ufffd\ufffd\ufffd\u003eu\ufffd]\ufffd9\ufffd\ufffd\ufffd\ufffd\ufffd;\ufffd\ufffd\ufffd\ufffd\u000f!g\ufffd\u0010r\ufffd\u0012!\u001f\ufffd\ufffd\ufffd\u0026\ufffd\ufffd\ufffd\u0014\ufffd\u001c\ufffd\u0008B\u00162\ufffd\ufffd\ufffd\ufffd{d\ufffd\u0005\ufffd(\u0005\ufffdq\ufffd~\ufffd\\\ufffdj\u001d\u0019\ufffdK\ufffd\ufffd\u000c\n2\ufffd\ufffd\u0000\ufffdt\ufffd\ufffd\ufffdHk\ufffd'K\ufffdL\ufffd\ufffd\ufffd\u001d\ufffd\ufffdf\u000e8\u0019F9r\ufffd\ufffdj\u003cI\ufffdLI\ufffd\ufffd\ufffd\ufffd\ufffdz\u000e4\ufffd\ufffdע˞\ufffdj\ufffd+\u0010\ufffd\u0005\ufffd\u0000uW\ufffd\ufffd\u0003 9\ufffd\ufffd\u0017\ufffd\ufffd\u0001\ufffdㅠ\u0004\ufffd\ufffd\ufffd\ufffd*\ufffd\u0000Ew\u0000\ufffd\ufffd\u001f\u000f:\ufffd[A\ufffd\ufffd-t\n@\ufffd\ufffdq\u001b\ufffd\u0002\ufffdU\ufffd\ufffdL\u001b\ufffd\u001e*}ivn\u0015\ufffd\ufffd\ufffdD\ufffd\ufffdQ\u0018 \ufffd\u003cS\u0004C\u0003\u0013Q\u0008\ufffd:`(\ufffd\ufffd֡\ro4\ufffd\ufffd\ufffdN\ufffd\n(,\ufffd\ufffdMq\r\ufffd\u0000@\r\ufffdd\u0015\ufffd\ufffd\ufffdZ\u0018\ufffd\ufffdS\u001d\u000e\ufffdx\ufffd\u00263\ufffd\u0015\ufffd\ufffdA\u000fH\ufffd\u0014S\u003e\u0013`}\u0007\u0018y\ufffd\ufffda\ufffd\ufffd\u0015F\u001e\ufffd¼|He\ufffd\ufffd\ufffd\ufffd\u0010\ufffd\ufffd\u0013JM\u0026\ufffd\u000by\ufffd\ufffdy\ufffdpx\ufffdo^I\ufffd+\ufffdֲ\ufffd,\\J6\u0006ˆ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd%A\ufffd\ufffd}\u0012\ufffd\ufffd\u0013*\ufffdѠ2\ufffd\ufffd|\ufffdu\u0012,\u0011\ufffd\u000bo\ufffd\ufffd\ufffdJ\ufffdK\ufffdDL\ufffd\u000e\u0006\ufffd\u0011Z\ufffd\ufffd\u0002x\ufffd\ufffd\"\ufffd\ufffd\ufffd0\ufffd\ufffd\u0013ai*\u000b5)`^\u0012\ufffd'd\u0013\ufffd\u0015\ufffd\ufffd\ufffd\u0003\ufffd\ufffd\ufffdx\ufffdy\ufffd\n0\ufffd\ufffd\ufffd\ufffdc\ufffd\u000b$\u0008\u0006J\ufffd\ufffd\u0015?s\u0010?\ufffd\u0013\u001f\ufffdx+\ufffd\ufffdcz\u0001X\ufffd\ufffd\ufffdR\u0005t\ufffd\ufffd.2E\ufffd,\u0003\ufffd\ufffd\ufffdK\ufffd\u000bN\u000e\u0008:!.ށ\ufffd0\ufffd\ufffd\u0006\t;\ufffd\u0026\u000f¼\ufffd~/%\u000b1\u0016\u0008|9+|DC\ufffd\u0007\ufffdS\u0011\ufffd\u0015r`\ufffd,\ufffd9\ufffd\u0007A\u0016\u0000\ufffd\ufffd;\t\ufffd\u0000\u0019E3\ufffd\ufffd\ufffd5\ufffd\ufffd\u0019H\ufffd\u0003\ufffd\u003c\ufffd\ufffd\ufffd\ufffdo\u0005\ufffd\ufffd\ufffd\ufffd\ufffd\u0010\ufffdǕ\ufffdH\u001c\ufffdJP\u0001\ufffdJo\ufffd\ufffd\ufffdB3:\u0016В\ufffd'o\ufffd\ufffd:\ufffd\ufffd\ufffd\ufffdCX\u0003\ufffd\u0015\ufffd0\ufffd\ufffdB\ufffd7P\ufffd\ufffd\ufffd\ufffdC\ufffd\ufffd|aPV\ufffdR\ufffd37\ufffdJ\ufffd-\ufffd\u000b~i\"\ufffd\ufffd\ufffd\ufffd\r\ufffd\ufffd\ufffd;\ufffd\ufffd\ufffdW\ufffd\ufffdx\t\ufffd\ufffd0E\ufffd\ufffd\ufffdp\u000f\u0004\ufffd\ufffd\u00001\ufffd=\ufffdV\ufffd\ufffd\ufffdon@nb\u000cNog^Pֱ\ufffdҰ\ufffd
\u0001\ufffd*\u0005\ufffd\ufffd\ufffd\ufffdPyjϒ6\ufffd\ufffd\ufffd\ufffdl\ufffd!\u0000\u001a\ufffd\n\ufffds2\u0004\ufffd\ufffd\ufffd/\ufffd\ufffdrLՀ\ufffd\u0000\u0010\ufffd\u00071\ufffd\u001a0et\ufffd\ufffd\ufffdF%s\u0000\u0016\ufffdͲRKW\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdו\ufffd\ufffd\ufffdk\ufffd#F\u001cC\u003eN|\ufffdUi%\ufffd\ufffd\ufffd\ufffd\ufffd6b\ufffd8]Hyi@\ufffd\u001e\ufffd\ufffd\u0016\ufffd\n\ufffd\ufffdF)\ufffd\ufffd\ufffd\u0017\\\ufffdN\ufffd3 \ufffd\u0000\ufffd\ufffd\ufffdB\u0016\ufffd}k\ufffd-J!˽\ufffd7\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0017YD'\ufffdw\ufffd\ufffdG\ufffd-Qh\ufffdK\"7U\ufffd\u001b\u0006\ufffd\ufffd\"\ufffd\ufffdg)\u000b\ufffd\u0001\ufffd\u001a!\ufffd\ufffd\ufffdP\ufffd\u0008\ufffd\ufffdRI\ufffd\ufffd7\ufffd$\ufffdj\u000f\ufffd\ufffd\ufffd\ufffd\ufffdB\ufffd$\ufffd\ufffd\u0000\ufffdm1\ufffd\u0016\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd~\u001e\ufffd\ufffd\ufffd.Ӌ\ufffd\ufffd^O\ufffd\ufffd\ufffd\ufffdwo\ufffdx\ufffd\ufffd˖E\ufffd\ufffd\ufffdt\ufffd\ufffd2\ufffd\\\ufffdL\ufffd\ufffd\ufffd!\ufffd@{\ufffd\u0015F\u0001\ufffdѝ\ufffdrHȕ\ufffd\ufffd\u001f\ufffd\ufffd|\u001fXa\ufffd\u0000Bt\u0016\ufffd4:V2\ufffd\ufffd\ufffd\ufffd{\ufffd/\ufffdH\u000fp\u001a\ufffd{H'\ufffdЪ\ufffd\ufffd)\ufffdĴ\ufffdяN\ufffd\ufffd\ufffd\ufffd\ufffd\ufffds:ad\t*\ufffd[\ufffd\u0014![͞\ufffd\ufffd\\\ufffd\ufffd\u0008\ufffd\ufffdn\ufffd\ufffd\ufffd2\ufffd\u0004\u003e\ufffd(l\ufffdw\ufffd\u0013S\ufffd\u0004R\ufffdނ\ufffd£Ԭk\ufffdK%,\ufffdFצb\ufffd\u001a\ufffd\ufffd\n\ufffd\ufffd\ufffdM\u0003u`*Eҁ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdc\ufffd\ufffd\ufffdo\ufffdt\u000b\ufffd|\ufffd4\ufffdU\u0015\nUaz\ufffd\ufffd\ufffdg\u0001\ufffd\ufffdJN\ufffd\u001b!\ufffdC\ufffd8\ufffd\ufffd\ufffd\ufffd\n\ufffdy\nS\ufffdWK{m\ufffd-\ufffd:tr{ᠶ\u000b\ufffd\u0016^\u000f\ufffd\ufffd\ufffd\ufffd-\ufffd7}\ufffdd\ufffd\ufffd\ufffd\ufffd6\ufffdj\ufffd\ufffd\ufffd\ufffd\ufffd\ufffda'?\ufffd\ufffd\ufffd\ufffd\ufffdp\u00198\ufffd7.\ufffd\ufffdX\ufffd\ufffd\ufffdKv\ufffd\ufffd^\ufffd\ufffd;\ufffd\ufffdq\ufffdր\u0004\ufffd\ufffd\ufffd\",y\ufffd\ufffd|nY\ufffd\ufffd\ufffdR\ufffd7\u0007n\ufffd\u000fm\ufffd\u001f\ufffdZ|7\ufffd\ufffd\ufffd\ufffd\ufffd{\ufffdjn\ufffd\u0015֮1\ufffd^wD\ufffd\ufffd\ufffd\ufffd 5|\ufffd\ufffd\u0019\ufffd\ufffd+\ufffd\ufffd\u001a\ufffd\u0004\ufffd\ufffd\ufffdI\ufffd?\ufffd\ufffd%C\ufffd*\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2646\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:04 GMT\r\nSet-Cookie: AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:07+05:30","url":"https://ginandjuice.shop/blog/?search=dadad\u0026back=%2Fblog%2F","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2","Referer":"https://ginandjuice.shop/blog","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/blog/","scheme":"https"},"raw":"GET /blog/?search=dadad\u0026back=%2Fblog%2F HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2\r\nReferer: https://ginandjuice.shop/blog\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2075","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:07 GMT","Set-Cookie":"AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/, AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ͒\ufffd6\u0012\ufffd\ufffd)`\ufffd\ufffd\u001cUB13\ufffd\ufffd\u000f#jk\ufffd\ufffdz+5v\ufffd;\ufffd\ufffd\ufffd\ufffd\ufffd@\ufffd\ufffd\u0007$\u0018\u0000\ufffd\ufffd\ufffd\ufffd\u0003\ufffd5\ufffddi\u0000$EI\ufffdH\ufffd\ufffdj\u000f\ufffd]%\u0012@7\u001a\ufffd\u000f\ufffd_C3{\ufffd\ufffd\ufffd'\ufffdz\ufffd\u000c\ufffdt\ufffd\ufffd_\ufffd\ufffd\u0017\ufffd\ufffdlEq\ufffd\u001e\ufffd+g\ufffd\u001dZI\ufffd\ufffdBI\ufffd($\ufffd*\ufffdxa\ufffdQ\u0019\u0012\ufffdBEp\ufffd\ufffd\ufffd\ufffd6L\ufffd\u0001I\ufffd#\ufffdKNՊR=\ufffd̨\u0000\ufffdꚋ\ufffd#\ufffdo\ufffd\ufffd\ufffd\u001eW\ufffdR\ufffdQ\ufffdS\u001aykF7\ufffd\ufffd\ufffdCDd\ufffdf:\ufffd6,֫(\ufffdkFh`_\ufffdF\ufffd\ufffd2\ufffd\u0005\ufffd\u000c\ufffdF\ufffd\ufffdZ\ufffd\u0014\ufffd,\ufffdHI\u0012y-\ufffd\ufffd+x\ufffdDOA\u0013\ufffd\"OA\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdI\ufffdW\u0011\ufffd\"=A\ufffd.sX\ufffd\ufffd\ufffd:|\ufffd\ufffdصz]\ufffdq\ufffd\u0014\u001c\ufffdw\ufffd\ufffd\ufffd\ufffdq\ufffdV\ufffd4\ufffd\ufffd\ufffd,C\u000fq\ufffd_\ufffd\ufffd\np\u000c\ufffd]\ufffd|\u0016\ufffd\u003e\ufffd\ufffdp\u000b\ufffd\ufffdB\ufffd%ʒ\u0000\ufffdyKS\ufffdֈő\ufffdFJ˓n\r\ufffdh\u00262D8V*\ufffd\u001c\ufffd\ufffd\ufffd:\ufffd\ufffdgO\ufffd\n\ufffd\ufffdm\ufffd\ufffdz\ufffd\u0014\ufffd\ufffd\u0018Ŕ\ufffd\u0005\ufffdXS^\ufffdu\ufffd3x\ufffd\ufffdD\u001b\ufffd@`#g\u0004\ufffdYa\u001e\ufffdd4FK!\ufffd\ufffdJ\ufffd,1\ufffd\u001ef\u000b\ufffd_Ղ\ufffd3]\ufffd\u0026\ufffd\u0014hS\ufffdN\u0003f*\ufffd\ufffdR\u000cl=$\ufffdT\u0000\ufffd`\u0017\ufffd4X\ufffdb\ufffd\ufffd\ufffd{w\ufffd\"t\ufffdt\ufffd\ufffdn\ufffd\ufffdNo\n\ufffd\ufffd\ufffdK\ufffd[wCk\ufffd\ufffd~\ufffd#\\\ufffd\ufffdǖ\ufffd\ufffd=\ufffdÇ\ufffd=\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\t\ufffd\ufffd\ufffdV\ufffd\ufffd\u0016\ufffd3\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdo9I\ufffd\ufffd\ufffd\ufffd\ufffdy\ufffd\ufffds\ufffd\ufffd\"\ufffd\ufffd\ufffd)66\ufffd\u003ePM[\ufffdί\ufffdA\ufffdEG\ufffdeG\ufffd#o\ufffd\ufffdШ\u0004\u003c\ufffdj\u001f\ufffd\u0016H\ufffd\ufffdݺ\u0007\ufffdep\u0007\\\ufffd=\ufffd\ufffd@\ufffd\ufffd\ufffdX\ufffd\u001dl\ufffd\ufffdD~\ufffdZ\u0003o7\ufffd\ufffd\u0012\ufffd`X7.g/?\ufffdPӡN٘\u0016@`\u0014p@\ufffd\ufffdGm\ufffd{:\ufffd\ufffd\ufffd\ufffd\r\u0006\ufffd\ufffd2\u0019\u0019Q\ufffd\u001a\ufffd0\ufffd%\u0016\u0013Au`\r\ufffd\u0015\ufffd%K\ufffd,\ufffd[\ufffd\ufffd=P\ufffd\ufffd\ufffd ;\u0010\ufffdS/\n\ufffd!(8\ufffdz! \u0013\ufffd\ufffd\u0000\u0019R\ufffd\u0005Ѫ\ufffd);\ufffd\ufffd1\u0013\ufffdu\ufffd\ufffd\ufffdh\u001a7f.\ufffd\ufffd\u0026\ufffd\ufffd?\ufffdט\ufffd\u0017\ufffd\ufffd\ufffd\ufffd{s\ufffd\ufffd\ufffd\ufffd'2n\u0016\u0016\ufffd\ufffd\ufffd\u0004\ufffdQd\ufffdgF\u0013\u0026D\u0014\ufffd\u000e\u0018iy%-\ufffd\ufffd\u001d\ufffd\ufffdZ'\ufffd\u0019\u0004\ufffd\ufffds\ufffdO\u0001\ufffd\ufffd\ufffd\u0002\ufffdz\ufffd\u0014F\ufffd\ufffd\u0013\ufffd/\u0008\ufffd4k\ufffd\ufffdqo\ufffd\ufffdq\u0003OZ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd^\ufffd\ufffdH\u0010\ufffdF[l\ufffd\ufffdB\ufffd\ufffd5\ufffdE\ufffd\ufffd\ufffd\ufffdd\ufffd0\ufffd\ufffd\ufffd\ufffd\u0000\ufffd\u0012,\ufffd\ufffdZE\ufffd\ufffd\ufffd\u0018\ufffd\ufffd\u0014x^\ufffd\ufffd5\ufffd\u0016\ufffd[\ufffd\ufffd\ufffdv\ufffd\ufffd[\u0007?\ufffd\"I8\ufffd\ufffd\ufffdN\ufffddIb\ufffd\ufffd[M\ufffd\ufffd\ufffd\u0017\ufffd\ufffdc\ufffd\ufffd,\ufffdM\ufffd=)\ufffd\ufffd\ufffd\ufffdX썲\ufffd\u0019\u0018\ufffd\ufffdE\\R8J\ufffdS\ufffd\ufffdM\ufffd\ufffdb\ufffd\ufffdL\ufffd5\ufffd\ufffd\u0002\u00079\ufffdnOȜ\ufffd0\ufffd8\\h\ufffd\ufffd\ufffdp\u0015\ufffd\ufffd\u0004C\ufffdݧ\ufffd\ufffdJp\ufffd\ufffdÎ\ufffde\ufffd\ufffd[\ufffd\ufffd4\ufffd\u0012\ufffdP\ufffd\ufffd\ufffd\ufffd䈀\ufffdX\ufffd\u0015\u0004)\ufffdɞT\ufffd\ufffd;~u\ufffdG\ufffd\u0000\ufffd@\n\ufffdĈD6y\ufffd\u0008\ufffdȕ\ufffd\ufffd\ufffd\ufffd^\u000f\ufffd\ufffdeyQ\u0015e\ufffd\u0026C9DŽ\ufffd\u0004\u0007\ufffdE\ufffd\ufffd\ufffd\ufffdrV\ufffdv:\ufffd\ufffd\ufffd2u\u0026\ufffd5\ufffd\ufffd\ufffd\ufffd\u0018\ufffd\ufffd\ufffdΖ\ufffd\u0015\ufffdc\ufffd9\ufffd\u0005\u0026w\ufffd\ufffd\ufffds O\ufffd*\ufffdb5\ufffdb\ufffd2]\ufffd\ufffdu̝\ufffd\ufffd\ufffdz=r^\ufffd\ufffd\ufffd\ufffd\ufffdɯ\ufffd\ufffdc;e\u003e\ufffd\"s\ufffd\ufffd%\ufffd\ufffdYt\ufffdKAe9A\ufffd;\ufffd\ufffdX\ufffd\ufffdV\ufffd\u001b\ufffd4=\ufffdg,M\u000e\n\ufffd*\ufffdY倞\ufffd-\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0014x\ufffdWv\ufffd\ufffd|o\ufffdO\ufffdz\ufffd\ufffd\ufffd\ufffdg\ufffd%\ufffd*\ufffd`:\ufffd\ufffd\u0006\ufffd\ufffd\ufffd\u001b\ufffd\ufffdW\u0006\ufffd\ufffd.\ufffd\ufffd\ufffdj\ufffdf\ufffdL\ufffd\t\ufffdg\ufffd{;65[\ufffd\ufffdơ\ufffdN]\ufffdI\ufffd\ufffd\u00064`kJ\ufffdL\u0004\ufffd\ufffd\ufffd\ufffdc\ufffd\ufffd\u001d\ufffdF\t\ufffd\u000b\ufffdIk0\ufffd%6\ufffd(\ufffd\ufffd\ufffd\ufffd\ufffd\\\ufffd[o5\u0026\ufffd.\ufffd\ufffdB\ufffd\ufffd\u0001z)\ufffd\ufffd\u0000-\ufffds\ufffd\u0010\ufffd.\ufffd͟_\ufffdQ\ufffdE\ufffdk\ufffd\ufffd5EE\ufffd\u0002DV\u0014\ufffd\ufffd\ufffd\u000b*\ufffd\ufffd\ufffd{\u0008s%\u0001 \u0002\u0013\ufffdi$\u0026L\ufffd*Nh\u0003\ufffd週C\ufffd[WB\ufffd0,ۄ\ufffdVЩ\ufffd\ufffd/}(\ufffd\tg\ufffd.\ufffd\ufffd\ufffd\u001c\ufffd\\\ufffd\ufffd\ufffdt\ufffda\ufffdg\ufffd\ufffd\ufffd%A\u0017\ufffd\ufffd\ufffdn\ufffd\ufffd\ufffd\ufffd\ufffd\t\ufffd\ufffdWT\ufffdHW\ufffd\ufffdڄ4(\ufffd\u0007\ufffd\ufffd\ufffd\ufffdqo\u0000\u001a\ufffd\ufffd\ufffd\ufffd\ufffd-\ufffd\u0000\ufffd\u0007\u001b\ufffd\ufffd\ufffd;\u0011\ufffd\u001d\u0005'ЗH\u000f5s\ufffd4\ufffd\ufffdȒ\u000ee\ufffd\ufffdT:v\ufffd\u0000\ufffd/a\ufffd$J\ufffdr\ufffdb\ufffd\u0003\ufffd \ufffd\ufffdc\ufffd\ufffdpN7\ufffd1;\ufffd\ufffd\ufffd\t\ufffd!s\ufffd\ufffd\u0011\ufffd\ufffd\u001b\u0016SDD\ufffd\u0016\u0019\ufffd%\u0012K\ufffdp\ufffdp\u0016\ufffd\ufffd\ufffd\ufffdp\ufffd3\ufffdh\ufffd\ufffd\ufffdWj\ufffd\ufffd\ufffdB\u0019\ufffd\u0003(\u0014\ufffd\ufffd*\ufffd\ufffd\ufffd\ufffdXk`\ufffd\ufffd\ufffd4\u0017F\ufffd\ufffd\u001b\u0011\ufffdCY*-Z\ufffd\ufffdhN\ufffd\ufffdӃ[\ufffd]\ufffd\ufffd3\ufffdz\ufffd/!\ufffd\u000e3\ufffd\ufffd\ufffd\ufffdⲖ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdb\ufffd\ufffdAE\u0008\ufffd\\(]5U\ufffdaKʷ\nF\ufffd\u0006I)\ufffd\ufffd\ufffdK\ufffd\u0014\ufffd\u001fwy\ufffd=\ufffd\ufffd\ufffd\ufffd\ufffd6\ufffd8\ufffd\ufffd\u000e\u0006\ufffd.\ufffd\ufffd#\u0012^u3N\ufffd\\z5\ufffdx\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\t}\ufffd\ufffd\ufffd\ufffd}|\ufffdCI\ufffde\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u000bzw\ufffdf$\ufffdث\ufffdZ\ufffdc~[{f\u000c\ufffd\u0018\nc\ufffd\ufffdvà\ufffdˁƺ\ufffd\u00132I%\ufffd\ufffd\u0007VH\ufffdP4\ufffd\ufffdY\u0007K\ufffd\ufffd\ufffd\ufffd~jM:\ufffdasbN\ufffdЪ\ufffd\ufffdɲ\ufffd\u0018a\ufffd\ufffd\u00062~\ufffd\ufffd\ufffd\ufffd\ufffd.\ufffd\ufffd\u0004\ufffd^Aj\ufffd\ufffd\ufffd\ufffdf\ufffdͥ\ufffd\ufffd\ufffdzuY\ufffdM^\ufffdK\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd1\u0010\ufffd\ufffd\ufffd\ufffd\ufffd\u0004\ufffd\ufffd\u000eԪ+\ufffd\ufffd\u00264FC\ufffd\ufffd\ufffd@\ufffd\ufffd\ufffd\u0003vd\ufffd7-ԁRF\ufffd\u0000\ufffdߝ\ufffd\ufffd\ufffdV\u0017\ufffd\ufffd\ufffd\u0018\ufffd\ufffd\ufffdt\ufffd\ufffd|\ufffd\ufffd\u003c\ufffd%P\t\ufffd\ufffd\ufffdޤS{kn~\ufffd\ufffd\r\ufffdx\u0002\ufffd\u003eZP\ufffdf\ufffdF\u0015\ufffdfG\u0008i~\ufffd;\u001akO0j\u0004?1\ufffdb\u001cE\ufffd\ufffd\u001el\ufffdd\ufffdj\u0008\ufffdmQ\ufffd\ufffd\ufffd\ufffd\ufffdK\u0005{\ufffd\u003e\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd;\ufffd\ufffd\ufffd\ufffd\u0012\ufffd\u0002\r\ufffdN\u0003\ufffd\ufffd\ufffdG0\ufffd\ufffdr|\ufffd\ufffd\ufffd+\ufffd~\ufffd\ufffdIH`\ufffd\ufffd\u0013h\ufffd\ufffd\ufffdnv$\ufffdW\ufffd\ufffd\ufffd\ufffd\u000c\\\ufffd\u000e9ﯟ\"\u003e\ufffdu\ufffd\u0014\ufffd\ufffd/\ufffd\ufffdk\ufffd\ufffd[\ufffd\ufffd\ufffd\u001ev\ufffd\u000e\ufffd%\ufffd\ufffd\ufffd\u0018\u000e\ufffd)d%\u0011\ufffd\ufffd/ 7ڿ\ufffd\ufffd\u0013o\u0004\ufffdo\ufffd!\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2075\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:07 GMT\r\nSet-Cookie: AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:08+05:30","url":"https://ginandjuice.shop/logger","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Content-Length":"34","Content-Type":"text/plain;charset=UTF-8","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/blog/?search=dadad\u0026back=%2Fblog%2F","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"same-origin","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/logger","scheme":"https"},"body":"{\"search\":\"dadad\",\"back\":\"/blog/\"}","raw":"POST /logger HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nContent-Length: 34\r\nContent-Type: text/plain;charset=UTF-8\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/blog/?search=dadad\u0026back=%2Fblog%2F\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: same-origin\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"0","Date":"Thu, 07 Sep 2023 15:34:08 GMT","Set-Cookie":"AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/, AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\nContent-Encoding: gzip\r\nDate: Thu, 07 Sep 2023 15:34:08 GMT\r\nSet-Cookie: AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:10+05:30","url":"https://ginandjuice.shop/blog/","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g","Referer":"https://ginandjuice.shop/blog/?search=dadad\u0026back=%2Fblog%2F","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/blog/","scheme":"https"},"raw":"GET /blog/ HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g\r\nReferer: https://ginandjuice.shop/blog/?search=dadad\u0026back=%2Fblog%2F\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2651","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:10 GMT","Set-Cookie":"AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/, AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZێۺ\u0015}?_\ufffd\ufffdh\u0026\u0001\ufffd֙KrZ\ufffd\ufffdAng2An\ufffd\u0004Mӗ\ufffd\ufffdh\ufffd\u0019\ufffdTEʎ\ufffd\ufffd\ufffd\ufffd'\ufffd7\ufffd)\ufffd\ufffd\ufffdM\ufffd\u001e\ufffd\ufffd\ufffd\u0026\ufffd\u0000y8A0\ufffd\ufffd\ufffd\ufffdM\ufffd\ufffd}\ufffd\u0006w\ufffd\ufffd}\ufffd\ufffd\ufffd\ufffd\ufffd,s\ufffd\u001a\ufffd4\u0008?\u000c\ufffd\u0006\ufffd\ufffdix\ufffd\ufffdJ\ufffdK\ufffd\ufffdb2\ufffdKaMU\u0026\ufffdƊ\ufffd\ufffdL\ufffdqbml\u0013\ufffd_\ufffd\u0017\ufffd\ufffd\ufffd\u0006V\n5\ufffd\ufffdV\ufffdfB\ufffd}̈\u0005\u0018\ufffd'\ufffdL\ufffda\ufffd\u0005\ufffd\ufffd\ufffdn\u0006\ufffdp\ufffdi\ufffd\ufffda4\ufffdb^\ufffd\ufffdE,1\ufffd\t\ufffd\ufffd\ufffd\\\ufffd.\u001b\ufffdb\u0026\u0013\ufffd\ufffd/\ufffd\ufffd\ufffd\ufffd\ufffda\ufffd\ufffdA\ufffd\ufffd6Q\ufffd\ufffdMJY8f\ufffdd\u0018\ufffd\u0004\ufffdl\ufffd\ufffd\u0013\ufffd\u0007'\ufffdL\ufffd\ufffdy\ufffd\ufffd\ufffdF\ufffd8\ufffd\ufffd\ufffd\ufffd\ufffd\u0006l\\]`eN|q\ufffdg\u003e\ufffd\ufffd5\ufffdĝ\ufffdi\ufffdx\ufffd\ufffd\ufffd\ufffdK\ufffd\ufffdm\\\ufffdtJ\ufffdH-\ufffd\ufffdΤfwy^\u003cb/+\ufffd\u000f\ufffd\ufffdL1\ufffd\u0003I\u0000N|\ufffd\ufffd\ufffdؤ5\ufffd\ufffd\u001e/\ufffd\u0016\ufffdTΘL\ufffdQ\u001b0\ufffd\r\rK\u0011\ufffd\ufffdF\ufffdDqk\ufffdQ\ufffdY/\u0015A:\ufffd\ufffd\r\ufffd\ufffd\ufffd\ufffdm\ufffd\ufffd}\u0026-\ufffd\ufffdR\ufffd\ufffdX\ufffd\ufffd\tU\ufffdY\ufffd4\ufffd\ufffdP6\u0017c\u0006\u0019\ufffdL\ufffd\ufffd\u0015\ufffdȩ\u0016)\ufffd\ufffd\ufffd9a\ufffd\ufffdS\"\ufffd\ufffdǶx\ufffd\u0018(\ufffdtuh\ufffd\ufffd\ufffd\ufffd\ufffd7\n0\ufffd\u0005_.\ufffd\ufffd\u001b1\ufffds\u0003PA\ufffd\ufffd$\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u003e}9bCv\ufffd\ufffd6\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u001c\ufffd\ufffdޓл\ufffdF\ufffdnw\ufffd\ufffd\u000c\ufffd\ufffda$'\ufffd^{\ufffd\ufffdwY{\ufffd\ufffdד\ufffd\ufffd\ufffd\ufffdL\ufffdf\ufffd\ufffd\ufffd;3\ufffdw\ufffd\ufffd\ufffd\ufffdG\ufffd\ufffd\u0007*\ufffd\ufffd\ufffd×\"=S*\ufffd\u000f\ufffd\u0007\ufffd\ufffd\ufffd*\ufffdIf\ufffdA8\ufffdbw\ufffd\ufffd]k;\ufffd\ufffdv\ufffd\ufffd\ufffd4\u001a\ufffd\ufffd\u001c\ufffd\u0001k\ufffd\ufffd\ufffd9\ufffd\\\u0026\ufffd\ufffd\ufffd\u0000\ufffd\ufffd\ufffd\rp\ufffd\ufffd\ufffd\u0002\ufffd\ufffdX\u003c\u0008\ufffd}\ufffd':\ufffd\ufffd\ufffd\ufffd\u001dHɤp\t\ufffd\u001c\ufffd`\ufffd\u000e\ufffd\rl6\ufffd\ufffd\ufffdp\u0010\r\ufffd)ڷ\ufffd\ufffd\ufffd\ufffd\ufffd+V\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdh\ufffde\ufffd%E\ufffd\u0016\ufffd\u0006\ufffd\ufffdzL\ufffd\ufffd\u0003K`\ufffd\ufffd(e\ufffd\ufffd:\ufffdm8\ufffd\ufffdX*\ufffd\ufffd\ufffd\u0013\ufffd\ufffd\ufffd\ufffd\ufffd9\u0018\ufffd\ufffd\ufffdQ\u000cdr\ufffd\u0013\ufffdQ\ufffd\ufffdJ\ufffdݺ)+\ufffd\ufffd.\u0013\ufffdt\ufffd\ufffd\ufffd8\ufffd.\ufffd\u001c{\u0019ɴ\ufffd\u0008\ufffd-\ufffd\ufffdcS\ufffdh\ufffd\ufffd\u000e\ufffd3e\ufffd\ufffd\ufffd\u001bĕ\ufffdf,\ufffd0\u001a\ufffd\ufffd2\ufffdx\ufffd\ufffdJ\ufffd\ufffdLZ\ufffd\ufffd\ufffd\u001dn\ufffdΦ\ufffd\u0001\u000cڢ\ufffdʭ\u0002\ufffdS\ufffd\ufffd\ufffd\u0005}\u001f\ufffdX\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdm\ufffdO\ufffd\ufffdm\ufffd\ufffd\u001bፖ}}\ufffdkph/\ufffd\u0015\ufffd\u0000\ufffd;K\ufffd\ufffdvB\ufffd\ufffd\ufffd\ufffd\ufffdf\ufffd\ufffd-\ufffd\ufffd\u001f\ufffd\ufffd\ufffd}\u0005`\u0013^\ufffd\ufffd\ufffd\ufffd\ufffd1\ufffd\u0010^\ufffd\u0005\u001e5\ufffd\ufffd#zi\ufffd\ufffd\ufffd\u0015\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd[\u0007\ufffd\ufffd\ufffdt\ufffd\u0004f\ufffd+\ufffd\ufffd\ufffdN)T\u000c\ufffdY.f\ufffd\u000b\ufffd\ufffdV\ufffd4\ufffd\ufffd:\ufffd-.xC\ufffd\ufffd\ufffdb\ufffdʻfD,\ufffd\ufffd\u0004\ufffd\ufffd3\u001c\ufffdᵗ\ufffd{S$|\ufffd\ufffd\ufffd%\u001dB\ufffd^\u0001\ufffdn1\ufffd\ufffd`f\ufffd\u001bn\ufffd\ufffd4\ufffdpc\ufffd\ufffd\r\ufffdC\ufffd6\ufffdG\ufffd\ufffd\ufffdÖ\ufffd\ufffd*\ufffd9X\ufffdORQ\ufffd\ufffd\ufffd\ufffdL\u000eZ\ufffdɎ\u0001V\ufffd2\ufffd`\ufffd\ufffd\ufffd\ufffdQ\ufffd\ufffd^\ufffd\ufffd0z\u0007\u0018\ufffd\u000e\ufffd0$4d\ufffd\ufffdw̐Kf\u0026\u001d\ufffd=\ufffd\u0007fR\u0017U\ufffd\ufffdQj\ufffd\n\ufffd\u0013\ufffd\u0019\ufffd\ufffd\u001b\u001e\\\ufffd\ufffd}\ufffdJl\ufffd\ufffd\ufffdAHP\ufffdEj\ufffd\ufffdd\ufffd\n\u001d\ufffd\ufffd\u003c\ufffdd3\ufffd*ф\u0015\ufffd\u001e\ufffd8hb\u0012\ufffd\ufffdV\ufffd\\\ufffdf\u0017B\ufffd(\ufffd4\ufffd\ufffd\ufffd\u001d'\ufffdve\u001b46F\ufffd\ufffd\ufffd5X\ufffd\ufffdp\ufffd\ufffd퐽\rmO_\ufffd\ufffd\ufffd\ufffd\u001dl\ufffdD\ufffd+\ufffd9O\ufffd'\ufffd$2\ufffd6\ufffd\ufffd\ufffd|q\ufffd\ufffd\ufffdhm\ufffd\ufffd\ufffd\ufffd\ufffdF\ufffd\ufffd\ufffd ;\u001e=f/\ufffd,k\ufffd\ufffd#^\ufffd\ufffdv\ufffdb\ufffdѰ\ufffdB\ufffdy\ufffd=\ufffd\ufffd2\ufffde\ufffd\u0014\ufffdOqN\ufffd\ufffd\ufffdv\u003e\ufffd\u0011JQ^\ufffd|@\ufffd\ufffd\u0019w,C\ufffd*(5E\"Np\ufffd\u0000M\ufffd'\ufffd?%Ky\ufffdg\ufffd\ufffd\u003e\ufffd|,\ufffd\n\ufffd\ufffdW\u000bvr\ufffd9F9%\ufffd\ufffd\ufffd1\ufffdT\ufffdr\ufffd\ufffd\ufffd;e\ufffd\ufffd:\ufffd\ufffdX\ufffdI\ufffdLX\u0004\ufffd}va\ufffd\u000fx\ufffd\u0018\u0019t\ufffd\u0019-X\u0002B\ufffd\ufffdOk۾\u001a\ufffd\ufffd\ufffd\ufffd\u001c\u0003\ufffd]\ufffd\ufffd\ufffd\u0014sF\ufffd\ufffdw{{\ufffd㻿\u001fBN;!\ufffd\ufffd#B\ufffdCIOM\ufffd\ufffd/)\u000eك\u0011\ufffd,d\ufffd\ufffdG\ufffdw\ufffdxK\ufffdA\n2\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd^\ufffd:2b\ufffdA\ufffd9\u0014d\u0026\u0013\u0001\u003c\ufffd\ufffd%\ufffd\ufffd\ufffd\u0002O\ufffd\u0014\ufffd\u001a\r];v\ufffd\ufffd\u001cp2\ufffdr\ufffd\ufffd\ufffd\ufffdx\ufffd\ufffd\ufffd\ufffd\ufffd1\u0001i!\ufffd\u001ch\u0012%/E\ufffd=\ufffd\ufffd\ufffd\ufffd \ufffd\u000bp\u0001\ufffd\u003eW\u0016\u000f\ufffd\ufffdL\u0012_\ufffd'\u0001D\u000f\u0017\ufffd\u0012\ufffd\u001a\u0012\ufffd\ufffd@\u0002\u0014\ufffd\u0002tN\u003c\ufffd\u003c\ufffd\u0004\ufffdӮ\ufffd)\u0001\u0005:\ufffd]\ufffd\u000bpVӎ3m\u003cz\ufffd\ufffd\ufffdٹUPg\ufffd:\n\u003c\ufffd\ufffd\u0000\t\ufffd\ufffd2\u0018\u001a\ufffd\ufffdR`\ufffd\u0001C\ufffd5\ufffd\u000emx\ufffdA\ufffdWt\ufffdV@a\ufffdnn\ufffdK@\u0007\u0000jA%\ufffd\u0001\u001e\ufffd\ufffd\ufffdPe\ufffd\ufffdp\ufffd\ufffd\u000b4\ufffd\t\ufffd\u0018L\rz@\ufffd\ufffd\u0000\ufffd[\ufffd\ufffd\ufffd\u001f\u000f#ǝ0\ufffd\ufffd\u0006\ufffd\ufffd}\u0026˔}ȄP]\ufffdPfrI^Ȼ\u001c\ufffd\u001b\ufffd\ufffd\u001bs\u0026\ufffd\ufffd\ufffdZ\ufffdƲt\u0019\ufffd\u0018,\u001b\ufffd\u0016\ufffd\ufffd߸\ufffd\u0014\ufffd\ufffd\ufffdI\ufffd\"N\ufffd\ufffdF\ufffd\ufffd\ufffds\ufffdi\ufffdI\ufffdD\u003c/\ufffd\ufffd\ufffdz*\ufffd.\u0019\u00121\ufffd;\u00184Gh\ufffd\u0014\u000b\ufffd1[\ufffdD\ufffd\ufffdǬ7N\ufffd\ufffd\ufffd,դ\ufffdyI\u0019\ufffd\ufffdM$WD^\ufffd\u0016\u0000s\ufffd\ufffd\u0001\ufffdA'\ufffd\u001cu\u0004L\u0013{\ufffdF\ufffd`\ufffd\ufffd\ufffd]\ufffd3{\ufffds\u003e\"\ufffd8\ufffd\u0017\ufffd\u0005\ufffd\u000e-u@ת\ufffd\"S\ufffd\ufffd2P\ufffd۸\ufffd\ufffd\ufffd䀠\u0013\ufffd\ufffd\u001d\u0018\u000cC\ufffdm\ufffd\ufffd\ufffdk\ufffd ,*\ufffd\ufffd2\ufffd\u0010c\ufffd\ufffd\ufffd\ufffd\ufffdG4\ufffd~\ufffd$\u0013!\\!\u0007FȂ\ufffd\u0003~\u0010d\u0001P~\ufffd\ufffd\ufffd\u000c\ufffdQ4\u0003\ufffd\ufffd\\3\ufffd\ufffd\ufffd\ufffd[\u0000σ\u001f\u000f\u003cG\ufffd\ufffds\ufffd\u0011\u003c\ufffd\ufffd\ufffdq\ufffd7\u0012\ufffd\ufffd\u0012T\ufffd\ufffd\ufffd[d\ufffdЌN\u0004\ufffd\ufffd\ufffd\ufffd\ufffd'\ufffd\tp\u003c\ufffd\ufffd\u0010\ufffd@se\ufffd\ufffd\"\ufffd\ufffd\ufffdP\ufffd\rT\ufffd+\u003c\ufffd\ufffd58_\u0018\ufffd\u0015\ufffd\u0014\ufffd\ufffdM\ufffdRf+\ufffd\ufffd_\ufffd\u0008=xx\ufffdc\ufffd\ufffdνo\ufffd\u0015h\"^\u0002\ufffd8L\u0011\ufffd\ufffd\u003c\ufffd\u0003A\ufffd[@\ufffd\ufffdw@̎|\ufffd[Y\ufffd7\ufffd 71\u0006\ufffd\ufffd7/)\ufffd\ufffdXiXK\ufffdm\ufffd\ufffd뜕\ufffdPyjǒ\ufffd\ufffdI\ufffd\ufffdٮC\u00004\ufffd\u0011\ufffd\ufffd\ufffd\u0008~\ufffd\ufffd_\ufffd#\ufffd\u0001c\u0001 \ufffd\ufffdb\ufffd%`\ufffd\ufffdR\t\ufffd\ufffdJ\ufffd\u0000,\ufffd\ufffd畖\ufffd\u0026\u000b5E7\ufffd\ufffd\ufffd\u001e\ufffd\u0013\ufffd\u0001\ufffd\u0004q\u000c\ufffd8\ufffd%Q\ufffd\ufffd@\ufffd\u0016soۈ\ufffd\ufffdt!\ufffd\ufffd\u0001\u001dz\ufffd\ufffdZp+\ufffd\ufffd\u0018\ufffd\ufffdvK_p\ufffd;q\ufffd=8\ufffdQ\nY,\ufffd\ufffd\ufffdw(\ufffd,\ufffd\ufffd\ufffd\ufffd.\ufffdFW[_\ufffd\u0011\ufffd\u0000\ufffdk*\u001e\ufffd\ufffdD\ufffd\ufffd)\ufffd\\U\u001d\ufffd\u0018t\ufffd\ufffd\ufffd\ufffd\u001f\ufffd,)\u0006\ufffdj\ufffd@ޣBY#\u003c\ufffdK%\ufffds\ufffd\ufffd\ufffd\u0014\ufffd\ufffdW\ufffd\ufffd\ufffd\u003e\nu\ufffd\ufffd\ufffd\u0002 \ufffd\ufffd$Z\ufffdN.N\ufffd,\ufffdt~\ufffd\ufffd\ufffd\ufffd_\u0026\u0017g\ufffd\ufffd\ufffd\ufffd?\ufffd\u003e\ufffd\ufffd\ufffd7\ufffd/\ufffd\u0007\u001f_\ufffd\u000f\u001d\ufffd*k\u0005\ufffdV\ufffdet\ufffdؙ.\ufffd\ufffd}\ufffd\ufffd\ufffd\ufffd+\ufffd\u0002j\ufffd{\ufffde\ufffd\ufffd+\ufffdA?n\ufffd\ufffd\ufffd\u0004\u0001\ufffd\ufffd-\ufffdit\ufffddr\ufffd4?\ufffd\ufffd\ufffdy\ufffd\ufffd\ufffd4jw\ufffdN\ufffd\ufffdU\ufffd\u0010S\ufffd\ufffd\ufffd \ufffd\u001f\ufffd\u001d\ufffd\ufffd\ufffd#\ufffd鄑%\ufffd)o\ufffdQ\ufffd\ufffd4{Q\ufffdr\ufffd\ufffdE8itS\ufffd\ufffd\ufffdA/\ufffd\ufffdFac\ufffd3\ufffd\ufffdJ\ufffd\ufffd\ufffd\ufffd\u0006l\u0017\u001e\ufffdaݰ]*a)4\ufffd\ufffd+\u0006\ufffda\ufffd\ufffd\ufffd\ufffd\ufffdP\u0007\ufffdR\ufffd=X\ufffd\ufffdhm\ufffdZ]\ufffd9v\ufffd\ufffd\ufffd\ufffd\ufffdn@Z\ufffd\u001e\u0017\ufffd\ufffdC\ufffd*LOў\ufffd,\ufffd\ufffdR)\ufffdw#\ufffdu\u0008\u001e'\u00142\ufffdU\ufffd6OaJ\ufffdji\ufffd\ufffd\ufffd\ufffdP\ufffdNn\u001c\u000ej\ufffd\ufffdo\ufffd\ufffd\ufffd:\u001f\ufffd\ufffd\ufffdxӗMv\ufffdoM\ufffdg\u0003\ufffd6\ufffd\u0018\ufffd\ufffd\ufffd\u001fv\ufffd\ufffd\ufffd\t{\u0007\u000e\u0017\ufffd\u0003{\ufffd\ufffd~\ufffd\ufffd\ufffd\ufffd\ufffdd\ufffdj\ufffd\ufffdn\ufffdc\ufffd\u001e\ufffdl\u000cH\ufffd\ufffd\ufffd\r7\ufffd\ufffd玕hk(U}\ufffd\ufffdFz\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw\ufffd\ufffd\ufffdo-\ufffd\ufffd\ufffd\ufffd\ufffd\\a\ufffd\u001as\ufffduKt\u001a\u000e\ufffd\u000fR\ufffdך\ufffd\ufffdSx%\ufffd֣\ufffd\ufffd\u001b\ufffd7\ufffd\ufffd\u0007ts\ufffdѫ*\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2651\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:10 GMT\r\nSet-Cookie: AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:13+05:30","url":"https://ginandjuice.shop/blog/post?postId=3","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+","Referer":"https://ginandjuice.shop/blog/","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/blog/post","scheme":"https"},"raw":"GET /blog/post?postId=3 HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+\r\nReferer: https://ginandjuice.shop/blog/\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"3942","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:13 GMT","Set-Cookie":"AWSALB=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/, AWSALBCORS=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd[\ufffdn\u001c7\ufffd}\ufffd\ufffd\ufffd;\u0018\ufffd\u0006\ufffd\u0012I\ufffd\u003cHj×\ufffd\ufffd\u001c[\u0016\ufffd\u001e\u0004\ufffd\ufffd\ufffd]\ufffd\ufffd\ufffdUU\ufffd!Yݮ\u0003?\ufffd3f\ufffd9\u001fp~\ufffd|J\ufffd䬽\ufffd*UK}\ufffd\u0012\u0007\u0018`\ufffd\u0008\ufffd.\u0016\ufffd\ufffd/k\ufffdX\ufffd\ufffd\ufffd\u0017o\ufffd\ufffd\ufffd\ufffd\ufffd;\ufffd\ufffd\"\ufffd\ufffd\ufffd4\ufffd%\ufffd\ufffd4S2\r?\ufffd1\ufffd\ufffd\ufffdȬ\ufffd\ufffdM\ufffdr\ufffd\ufffd\ufffdr\ufffd\\\ufffdh\ufffd\ufffd\ufffdĹ\ufffdKd\ufffdF\ufffd\ufffd\ufffd1\u0006\ufffdU\ufffd\ufffd\ufffdM\ufffd\\\ufffd\ufffd\ufffdG\ufffdH\ufffd\ufffd{\u00072\ufffd\ufffdM\ufffdP^\ufffdR\u0016\ufffdl\ufffd\ufffdjU\u0019\ufffd\u0007\"1\ufffdW\ufffd?\u001b\ufffdt곳T-u\ufffdF\ufffd0\u0014\ufffdSv\u0004\u000e\ufffdC\ufffd\ufffdJ3\ufffdQs\ufffdՕ\u0017\ufffd\u0026g\ufffd\u001eC\u001f\u001c\u001ed\ufffdǠ\ufffdrS\u0015 \u003e\ufffd\ufffd\u0006\ufffd\ufffdIXqw\u0012\ufffd\ufffd\u0014\ufffd \ufffd\ufffd\n\ufffdy\ufffd\ufffdO\u003eȥ\u000c\ufffd\ufffdM\ufffde\ufffd\ufffdsi:\u001c};\ufffdv\u001bU\ufffd}\ufffd\ufffdO\ufffd+\ufffdm#^\ufffdF\ufffdij\ufffd,\ufffd\ufffdK]\ufffd\ufffd\ufffd\ufffdN\ufffd\ufffd5\ufffd%\ufffde\ufffd:\ufffd\ufffd\u0005\u0001\u0007\ufffdk \ufffd\ufffdLڈr1\ufffdU\ufffd#\ufffd\ufffd\ufffd\ufffd\ufffd٠o\ufffd\ufffdz\ufffd`*\ufffdڔ\"ɥsg\ufffd\ufffd\ufffdQ\ufffd\u0002\ufffdxsc\u0001/\ufffdn\ufffdџ\ufffd\ufffdv\u0002\ufffdI\ufffd\ufffd\\ϔ\ufffd^\ufffd\ufffdX\ufffdy\ufffd\ufffd0\ufffdX\ufffd\ufffd\u0000\ufffd\ufffdN$\ufffd\ufffd}\ufffd\ufffdT\ufffd\ufffd\u001b+\ufffdr^\ufffd\u000b\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd]\ufffds\ufffd\ufffd0D\ufffd`̍72p\ufffd*ىBX\u001e\u0008S\u0016\u0006\u0010\ufffdi\ufffd%\u0000\ufffd\ufffdY\ufffd\ufffd\ufffd\ufffd\ufffd8\u0013\ufffd\ufffdk\ufffd\\{\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd}\ufffdѲ\ufffd\ufffd\ufffd\ufffd\ufffduu6\ufffds\ufffd\ufffd\ufffd\ufffdÇ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdW\ufffdEn\ufffd\ufffd΄\ufffdf\ufffd\ufffd\ufffd\ufffd\ufffd_\u000fg/\ufffdΓ\ufffdOZ\ufffd\ufffd\ufffdJ_\ufffdy\ufffd:;;x\ufffd\ufffd\ufffd+I\u003c\ufffd\u000eʫ\u001e\ufffd\ufffd\u0013qk\ufffdh\ufffd\ufffd\u0006\ufffd\ufffdڋ\u0006 \u0017\ufffd\u000e\ufffd\u001b\ufffd3Ŧ{\u0000\ufffdC\ufffd\u001b\ufffd2\ufffd\ufffd\u0017L\u000c\ufffdۉŃ\u0000\ufffdg\ufffd\ufffd\ufffd\u0006\ufffd\u0004\ufffd0\ufffd\u0002\ufffdԘ\ufffd\u0019\ufffd\ufffd2Ĵ\ufffd\ufffd\u00062\u001b\ufffd9\u000et#8\ufffd\ufffd\ufffdb|\u000bى\ufffd\ufffd\ufffd\ufffd\u0014;\ufffdh\u000f\ufffd\ufffd\ufffd[\ufffd\ufffdf\ufffdy\u000bm\ufffd\ufffd\u000b\ufffd\ufffd(:,\ufffd݉\ufffd\ufffdB\ufffd\u0026\u003cmp\ufffd[$s\ufffd\u0012O\ufffd\ufffdֳ\ufffd{\u0004\ufffd\ufffd\ufffd\ufffd\u0004Ȕ\ufffd\u0013\ufffdaMZ'\ufffdmU\ufffd\u001a\ufffd\ufffd]6\ufffd\ufffd\ufffd\t\ufffd\ufffdUڱ9c\u001e)\ufffd\ufffd+\ufffdױ%g\ufffd\ufffd\ufffd\ufffd[r(ol\ufffd\ufffd\ufffd;\ufffd\ufffd\ufffdo\ufffd\u0012\ufffdє\ufffd\ufffd\ufffd\u0026\ufffd$\ufffd.\ufffdH'=\ufffd\u0014\ufffd(\ufffd#i\ufffd\ufffdbz\ufffd\ufffd־\ufffdN\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdv\ufffd\u0018\ufffd\ufffdX\ufffd\t\ufffd\ufffdbʽD\ufffd.\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd{\ufffd}[\ufffd\u001bp\ufffd\u000b\ufffd\u001aU\ufffd.\ufffd\ufffd1ӽ\u0013j_\ufffd\ufffd4\"\ufffd\ufffd\ufffdXߏ\ufffd\ufffd\ufffd\ufffd\u0015\ufffdM\ufffd\ufffd\ufffd\ufffd\u0018\u0001'\ufffd\ufffd\ufffdJ)\ufffd0\ufffd\ufffd;\ufffd\ufffd\ufffd2t\ufffd\ufffd\ufffd\u001f\ufffdw\u0016\ufffd\ufffd\ufffd#o\u0016\ufffd\\a7\ufffd6j\ufffdbA\ufffdb\ufffd\ufffd\u0013f\ufffd\u0003\ufffd\ufffd\ufffdQ\ufffdt\ufffd5\u0001nI\ufffd\u001b\ufffd7\u0014\u00167fqjF\ufffdB\r\n%\ufffd\u00114\ufffd\u0013R\ufffdβ\ufffd@\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd:\ufffd\u000f\ufffdy(\ufffdG\u0015\ufffd\ufffd%t\ufffd\ufffdp\ufffd)\ufffdx=\ufffduq\ufffd\ufffd\ufffd\u0016N\ufffd\ufffd-o\ufffdC\ufffd\u000c-N\ufffdغ\ufffd\ufffd\n\ufffd\ufffd\u0005m\ufffd4\ufffd\u000c\ufffd\ufffdL\ufffd\ufffd[t}\ufffdz~ݿ\ufffd\ufffd\ufffdl\ufffd\ufffd\u001d0}u\ufffd\u0015+\ufffd\ufffd\ufffd\ufffdy\ufffde\ufffdnIv\ufffd\ufffd+\ufffd\ufffd\ufffd\u0010\t4\ufffd:b4k\ufffd3P?\u0015pS\ufffd\ufffd\ufffd\ufffdsis\ufffd\ufffd\u001b\ufffd\ufffd42\ufffd\ufffd\ufffd$\ufffd'\ufffd 2=\u003c\u0012O\ufffdE\rF\ufffd\ufffd\u003c:\ufffd\\gs\ufffd̜\ufffdb\u0011[K\ufffd\u0016\ufffdP\ufffd\ufffd\ufffd'$\ufffd\ufffd|=\ufffdP-\ufffdJ\ufffd\r!\ufffd\ufffdG#\ufffd\u001a\ufffd\ufffd\u0003\u0011\u0005ȔUB.\ufffd۱8\u0017+Yz.\ufffdU\ufffdS\ufffd/\ufffdP\u0011\ufffdLz\ufffd\ufffdSԲ\ufffdA\ufffdҟ\n\u0017ſ\u000c\ufffdgE*\ufffd\ufffdx#\ufffd!\ufffdqb\ufffd\u003cwu\u000e\ufffd\ufffd\"\ufffd\ufffdz-\ufffd=^C\ufffd5\ufffd\ufffd\ufffd\ufffd\ufffd?R1#\ufffdH\u0001\ufffdL*\ufffdʡ\ufffd\u001b\ufffdw\ufffd\ufffd\ufffd\u0006\u0026\ufffd\ufffd\ufffd2S*\ufffdH\ufffd\u0005VJxY\ufffdFa,\u001eA\ufffd\ufffd\u000e\ufffd\ufffd\ufffd\ufffd̘\ufffdV\u0002=\ufffd*]xQ)S\ufffd\u001b\ufffd`|a\ufffd*#*\ufffd\ufffdX\"\ufffdR;\ufffd\ufffd\"\ufffdTr%\ufffd\n=U\u0010\ufffd\ufffd,\ufffd\ufffd\ufffd\ufffd\ufffd4 y\ufffd8\ufffd\ufffd;\u0016(\ufffd\ufffd\ufffd\u0016\ufffd\u000f\ufffdc\ufffd+E[\ufffd#W\ufffdh\u0004:F\ufffd\u0019\ufffddC\u0026\ufffd\ufffd\ufffd|%\u001bh\ufffdx\ufffdR\ufffd\ufffd6\ufffd\ufffd\u0002\ufffd\ufffd\u0010\ufffdӨ\ufffdB^\ufffd2%6\ufffd`\ufffd\ufffdQ\ufffd\ufffd9\ufffdi\u0002/\u0019\ufffd\ufffd6i5\ufffd\nK\ufffd%:9+~\ufffd0\ufffd\u001d2]X\u001a\ufffd\ufffd\ufffd_\ufffdݭR\ufffd\ufffdK\ufffd\u003c\u0018\ufffd\ufffd\ufffdT^\ufffdF2\ufffd\ufffd49F\ufffd/=\ufffdvЊ*ɔ$5\u0018\ufffdp\ufffdN\ufffd+k\ufffd\ufffd\ufffdV\ufffd\ufffd\u0003a`G\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd-Iۿ\ufffd\ufffdw\ufffd\ufffd\ufffd2U\ufffd`x\ufffd\ufffd\u00131k\ufffd\ufffdrؚa\u0005\ufffd\u000c[\ufffdT)*c\ufffd\ufffd\ufffdJ\ufffd$\ufffd\u00120S\ufffde\u0001\ufffd8\ufffd\u0010\ufffd\u000b\ufffd\ufffd@E\ufffd\ufffdC\ufffd\ufffdN\u0000\ufffd%\u000b\ufffd\ufffdy5\u001dcd\u0018 \ufffd\u0010\u000c\ufffd/,\u000cʁ\u0026\u003e\ufffd\ufffdgƣ\ufffds\ufffdB\u001fjG+\u001d\ufffd8\ufffd\ufffd\ufffd08\ufffdFW\ufffd]\ufffd\u0001\ufffd8v\u0002\ufffd\u000f\ufffd\\F\"95\ufffd\ufffd\ufffd\ufffd!CD\n\ufffd\u0000\u0014\ufffdഠ\ufffd\ufffdg\u0001\ufffd\ufffd\\\ufffdWb.\ufffd\u0016k\ufffdFL\ufffd\t\ufffd\ufffd\ufffdӲ\t\ufffd\ufffd\ufffdt\ufffd*d2ca\ufffd\ufffdf\"Χn\ufffd\ufffd\ufffdY \ufffd\u00042^\ufffdfEk\ufffdbӚε@\ufffd\u0013bH\ufffd\ufffd\ufffdΌ\u001c2\u0008p\ufffd\ufffd]VL{\u001c\ufffd\ufffdA!}\ufffd\ufffd]\u0017\ufffd\ufffd\ufffd\u0005)ׇʒf\ufffd\ufffdj5\u001b\ufffd\ufffd\ufffd\ufffd\u0002~\u0012\ufffdӀ\ufffdaD\ufffd--\ufffd_\u0003(\ufffd\ufffdKl\ufffd\ufffd\u0011\u000c\u0004\ufffd\ufffdmx®\ufffd\ufffd!ۊ\ufffd\u0026\no\ufffd1, \ufffd\ufffdD\u0008{@\"\"\u0003\ufffdy\u0013\ufffd\ufffd\u0002b\ufffdo\ufffd\u0004re-\ufffdsc;\u0006\ufffd38E\ufffd\rǭ\u0010\u000c\ufffd\u001c=\u0007\ufffdT\ufffd\ufffdk\ufffd\ufffd.\ufffd\u0026_\ufffdy\ufffdکI\u0017\ufffdH\ufffd/K\ufffd8\ufffd\ufffd:\ufffd\u000bf\ufffd0 [ J\ufffd\ufffd\u0005m\u0002W\ufffd\ufffd\ufffd\ufffd|\ufffd\ufffd.\ufffdCԕW}\ufffd\ufffd\ufffd}\u00151\ufffd\u003e\ufffd\ufffd\ufffd$\ufffd\ufffdQ\ufffd\ufffdHn,\u0006\ufffd\u00036|aXk\ufffd\ufffd\ufffdj\ufffd\ufffd\ufffd\ufffd$\ufffd\u0018@\ufffd`\u000bC\ufffd\ufffd\u0008\ufffd?*\ufffd`\ufffd\ufffd\ufffdiz=\ufffd\u001b\ufffd֕\ufffd+\ufffd\u0016P\u0005Ŕ\ufffd)\ufffd\ufffd\u0003\ufffd\ufffdw\ufffd|(\ufffd\ufffdx!S\ufffd\ufffd\u0000\u001e\ufffd\u0012\ufffdd\u0008\ufffd\ufffd!HєY\ufffd\ufffd\u000bc:!\ufffd8#\ufffd\ufffd\ufffd\ufffd\u00033J@i)\ufffd\u0010BD]f\u0026o\ufffdz\ufffdb\u001agFb\ufffd\ufffd\u001a\ufffd\ufffd\ufffd4\u0026\u000fFR\u000cC+\ufffdƾ\ufffd\ufffd\ufffdo\ufffdku\ufffdeI\ufffd\ufffd{\n\ufffd\ufffd\\\u0016\ufffd\ufffd\ufffd\u003c\ufffd\u000bVl\ufffd\ufffd\ufffd\n\u001cϮ\u0003\u0019\ufffd\ufffd\ufffdH\ufffd\\\u0026\ufffd\ufffd\u0008\ufffdz\ufffd\ufffdx9\ufffdY\ufffd$D!\u0018Й\ufffdI\ufffd\ufffd6ף\nP\ufffd\u0019\u0016\ufffd\ufffd\ufffd\u0007\u003exf\ufffd \u0008\ufffd\ufffd\ufffd\u0005\ufffd\u0004w)\ufffdϠ̑eC\ufffd\u0007r\ufffd\u001b S\ufffd\u0005Q_\u000bS\ufffd\ufffd\ufffdN\ufffd\u0004\ufffd\ufffd$넅\ufffdb\u0010\ufffd\ufffdf\ufffd\ufffd%\ufffd\ufffd\ufffd\u000bF%\ufffd\u000eXI2\ufffd\ufffd1`\ufffdw\ufffd\ufffdr\ufffd\ufffd\ufffdԔ\ufffd(\ufffd\ufffdW\u0014t\ufffd\ufffd**\ufffd\u0000\ufffd\u00172\ufffdxR!-\"\ufffdk\ufffd2g\ufffd\ufffdT;S\ufffd\ufffd\u001b1\ufffd̞\ufffdJp\u0008\ufffdm\ufffdU1\u003e\u0007\u0001\ufffd)ڍ\u000e\ufffd\ufffd𘠂\u0000\ufffdB\ufffd9\u000f\u0018\ufffd\ufffd\ufffd\u0014\ufffdK\ufffd\ufffd8*\ufffd\u0004\u0006\ufffd\u0013֘\ufffd\ufffdi\ufffd\ufffd\ufffd\"\ufffd\ufffdt\u001c+'\nFe,T\ufffd\ufffd\ufffd\ufffd\u000c\ufffd\ufffd\ufffdg \ufffd\u0014BLp\u0012B\ufffd\ufffdLQ\ufffd\ufffd$4\ufffd~\ufffd\ufffd6\ufffd^\ufffd\ufffd\ufffd\ufffdI;-@\ufffd2(E\ufffd6\ufffd\ufffd\ufffd\ufffd1J\ufffdy\u001a\u00177\ufffdץ\ufffd\ufffd\ufffdy[Ǡ\ufffd\ufffd|\u003e\ufffd(\u0007\ufffdY{\ufffd#\ufffd#\ufffd-j\ufffd\u001f{\ufffd\u0013qަ\ufffdX4 \ufffd\ufffdP\u0008yJ`\"\ufffdЁ\r8\ufffd\u003e\ufffd\ufffd\ufffd\u0026\ufffdIr]͌\ufffd\ufffd\ufffd\u000f\u0011f\u001c\ufffd\u0002I\ufffd\u0019\ufffd|\ufffdp\ufffd\u0017\\\u000f\ufffd\ufffdq)c\ufffdX\ufffd\ufffd\ufffdl\n\ufffd\u0015p\u0010У]\ufffd\ufffd'!ER\u0015\u001a\u0012\u0004D%\ufffd\ufffdB\ufffd\ufffd\ufffd t \ufffd\u0015\ufffd#\ufffd\u001eV\ufffd4\u0008\ufffd\u0014a\u0015\ufffd̦u\ufffduB\ufffd\ufffd'\u0008D\ufffd\ufffd\ufffd%\u0005\ufffd\ufffd\ufffdw+*\ufffd\ufffd\t\ufffd\ufffd\ufffd'\ufffd\ufffd \ufffd1\ufffd\ufffd\ufffdNFe\ufffd\ufffd\ufffdg\u000f\ufffd\ufffd\u0014\u0015I]\u0015\u001c\ufffdh\ufffd\ufffd\u0010\ufffdP\ufffdH\ufffd1fo\ufffd\u00011\u0002r\ufffd7G\ufffd\u000f\ufffd`\ufffd'\ufffd\r[\u003e\u0010/\rj1\ufffd\u0000{\ufffd\ufffd\ufffd\ufffd-\u0004^\u0012\u0004\ufffd$\ufffdD4\ufffdN\ufffd_\\\ufffd\ufffd\ufffd\u0010\ufffdo\ufffdaN\ufffd\ufffd\u001eu\ufffdS\u0011t\u003c\u0026\ufffd^1/m\ufffd\ufffd\ufffdg\n\u0018\ufffd,\ufffdV1w(\ufffd:d\u0005\ufffd\u001a2C}À\ufffd\ufffdĺ\ufffd\ufffd\ufffd΄\u0013\ufffd\u0013\u0012\ufffdy\ufffd\ufffd\u001b\ufffdU\ufffd\ufffd\ufffd\ufffd\u001dT\ufffd\ufffdܤ,8fõU\ufffd~4\u000b.\ufffd6(\ufffd\ufffd\u00083\ufffd\u001b\ufffd\u0008\u0002\ufffd`\ufffd\u0017¾\r\ufffd\ufffdU\u0002\u0017c\ufffd\ufffd\ufffd\ufffdF\ufffd;\ufffd\ufffd\ufffd}\n\u001e\ufffd\ufffd\u000c\ufffd\ufffd2:\ufffd\ufffd+\ufffdC\ufffdL\ufffd8\ufffd\u0005]\ufffd\\8\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd@uȲĂk\ufffd\u0018|\u001fJ\ufffdЪ\ufffdk\ufffdmd\ufffd\ufffd1Ss\ufffd\ufffd\ufffd\ufffdX\ufffd\ufffd\ufffd\ufffd:\ufffd\ufffd/\ufffd\ufffdZU\ufffde\ufffd\n\ufffd\tz\u0002*\u0013\ufffd\ufffd\u001dۈ\ufffd\ufffd\"\ufffd(\ufffd5E?\ufffd\ufffdX\u0016\ufffd\u0013\ufffdR.\ufffdv)e\u000c\ufffd\u000b\ufffd\ufffd^6\ufffd\ufffd\u0005\ufffd\"\ufffd\ufffd@\ufffdK\u000cf\ufffd~2\ufffdJ*\u001a\u0010\ufffd\ufffd2Ĺ\ufffd\ufffd9\ufffd\u003ePQ\ufffd\ufffdH;RJ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd)\ufffd\ufffdݮc\ufffd\ufffd\u0013\ufffd$\ufffd\ufffdu\u000e\ufffd\ufffdG~w}\u0014q\ufffd\u0008S.%\u0002\u0008\ufffd_\u000e\ufffdc}\u001e\u001aL\ufffd\ufffd\ufffd?\u0017\u001a\ufffd\ufffd\ufffdCY\u0019w\ufffd\u003cӕ\u0013\ufffd\ufffd\ufffdq\ufffd\ufffdd\ufffdY\ufffd.V\ufffd\ufffd\ufffdt@qK/Ȳ\ufffd\r\u0010Wl7\ufffd\ufffdO\ufffd\ufffd\ufffdu-\ufffd\u001d\ufffd\ufffd2\ufffd\ufffd\ufffdo?\ufffdn\ufffdo\ufffd\ufffd\u001fFߞQ\u0004e_D\ufffd\t\u0006]r\ufffdD\ufffd/|\ufffd\ufffd\u0019\u003e\u0015z\ufffd_\ufffdpPT捈\ufffd[\ufffd\ufffd\ufffdö Q\ufffd\u0007\ufffdWtK\ufffd\ufffdF\ufffdЈho\ufffd\u0026\ufffdS\u001c\ufffd$\ufffd\u000f?\ufffdg\ufffd\ufffd\ufffd\ufffd1\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd3T\u003c\ufffd%r3\ufffd\u0008\ufffd\ufffd\ufffd\ufffdx\ufffd*\ufffd\ufffd\u00192\ufffdoR\ufffd\ufffd\ufffdJ\u0018\ufffd\ufffd\u001b\ufffd*\ufffdL\ufffdю\ufffdb\u0015\ufffdV\u003c\u0026E\nJ\ufffd\ufffdH!E\ufffd-e\ufffdBQ7\ufffd}(\u000b\ufffd\ufffdn\ufffd靟k\ufffd\u001fx)\ufffd\ufffd\ufffdO{\ufffd\ufffd\ufffd\u0003\u0008\ufffdz:\ufffd:;\ufffd\u003c\ufffd\u001f\ufffd\ufffd\ufffd\ufffd_\ufffd\ufffd\ufffds\ufffd\ufffd\ufffdI\ufffd\ufffdJ\ufffdV\ufffd/~\ufffdNQ\ufffdu)\ufffd,ܣ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd1j\ufffdG\u0003f\ufffd\ufffd\ufffd\ufffdI\ufffd\ufffd\t'\u0007\ufffdg\ufffdڡ\ufffd\ufffd\u001f\ufffd\ufffd硛ژ\u001b\ufffd\ufffdi\ufffd\ufffdtʽ\ufffdK\ufffd\rh\ufffd\u0005ێ\ufffdoS\ufffdzj\ufffd\ufffdV\ufffd\ufffd5\t\ufffd^\ufffde\ufffd\ufffdhz\ufffdek\ufffd]\ufffd%s1\u0012\ufffd\ufffd\ufffd=.\u0014\ufffd\ufffdXP+\u001c\ufffdD\ufffd\ufffdh\u0026\u0004\ufffdc\ufffdf\ufffd\ufffd\ufffd\ufffd#ǭK\ufffd\ufffd\u0016\ufffd\ufffdN\u0006B)N\ufffd\u0002s4\ufffd^'(\ufffd\ufffd}S\u001f\ufffd\ufffdv\ufffdtaq\ufffd\u000eL(\ufffdt1\ufffd\ufffd\ufffd\u003cU|V\ufffd\ufffdĠ\ufffd\ufffd\u0019\ufffdP\ufffd'\ufffdt\ufffd\ufffd'(\ufffd\ufffd\ufffd\u001d\ufffd߸넽0\ufffd\ufffd\u0007w\ufffd\ufffd\ufffd\ufffdF]\ufffd\r\ufffdv\ufffd\ufffdZ\ufffd\ufffd\u0018\ufffdZ\ufffdrDM\ufffd\ufffd$\ufffd\ufffd\u0012\ufffd$c\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd[\ufffd\ufffdo\ufffd\u000b\ufffd\ufffd\ufffd(n\ufffd\ufffdd\ufffd\ufffdǦh\ufffdR\ufffd\ufffd\ufffd\u001d\u000f\ufffd4\ufffd\ufffd\ufffd\ufffdal$?\u0008'\u000b\ufffdx/3qv\u003e\u0010K\ufffd\ufffdx\ufffd[\ufffd\ufffd\ufffd\"\ufffde\u0017\ufffdO\ufffd\\\ufffdy\ufffd\ufffd\ufffd.|u\ufffd\ufffd\ufffd]\ufffd\ufffdw\ufffd/\ufffd߷_\ufffdps\ufffdb\u0000o\u000b\ufffd\u0014\ufffdOߵ\ufffd9\ufffd\ufffd\ufffd\u0001o\ufffd\ufffdh-\ufffd5\u001b,1ue\ufffdQ\u0018\ufffd\ufffd\ufffd\ufffd\ufffdY^\ufffdݿ\ufffdH\ufffd\ufffd1٨\ufffd\ufffd\ufffd\ufffda\ufffd9\ufffd~\ufffd,=\ufffd\ufffd\ufffd\ufffd\u003c}\ufffd\ufffd;\ufffd\ufffd\ufffd\ufffd܁G^\ufffdF\ufffd\ufffd\ufffd?\ufffdy\ufffdus\ufffd\ufffd?\ufffd\ufffd\ufffdU\ufffd+g\ufffdf\ufffd8ڦ\ufffd\u0002n\u0014\ufffd\u000c\ufffdA1\ufffd)5u\ufffd\ufffd\ufffd\ufffd{\ufffd\ufffd\u0016hIG\ufffd\ufffd\u0011:\ufffd\ufffd\ufffd\ufffda0\u001a\ufffd\ufffd\u0015\u0016Y\ufffdM\u000fu \ufffdU:B\ufffd\ufffd\u001a\ufffd\ufffdY\ufffdU\u003c\ufffd\ufffd\u0019\ufffdo\ufffdt\ufffd\ufffd\ufffd\ufffdiU\ufffdBډ\ufffd=\ufffdJ\ufffd\ufffdC\ufffd\ufffd+\ufffd\ufffd\ufffdS\ufffd\u0013\ufffdn\ufffd*4\ufffd3P~\ufffd=}\ufffd=\ufffd\ufffd繓\ufffd\ufffdw\ufffd}\ufffde=\ufffd\ufffd\ufffd]\ufffd\ufffd\ufffdt\ufffdܭ\ufffd\ufffd\n_\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u000b?q\t\n\ufffd\u0002\u0005\ufffdڧ\ufffd;Hv\ufffdk+[\ufffd\ufffd\ufffdn-f\ufffd\ufffds\ufffd\u000b\u0012H\ufffd\ufffdGYr\ufffd]\u0002\ufffdUe\ufffd\ufffd9\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u000cܧ\ufffd\ufffd\ufffdy\ufffdl\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd]\ufffd\ufffdW\ufffdq\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd48\u0019\u0017\ufffd\ufffd\ufffd\ufffd\ufffd.O\ufffd\ufffdL\ufffdL\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0001j\ufffdl#\u00024\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 3942\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:13 GMT\r\nSet-Cookie: AWSALB=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:18+05:30","url":"https://ginandjuice.shop/about","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD; AWSALBCORS=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD","Referer":"https://ginandjuice.shop/blog/post?postId=3","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/about","scheme":"https"},"raw":"GET /about HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD; AWSALBCORS=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD\r\nReferer: https://ginandjuice.shop/blog/post?postId=3\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2591","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:19 GMT","Set-Cookie":"AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/, AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ\ufffdr۸\u0019\ufffdߧ\ufffd\ufffd\ufffd(\ufffd\tŵ\ufffd6\ufffdF\ufffdL\u000e\ufffd\ufffdns\ufffd؝4\ufffd\ufffd@$$\ufffd\u0006\t\ufffd\u0000%\ufffd\ufffd}\ufffd\ufffd\ufffd5\ufffd(}\ufffd~?@ɔDIT\ufffd̴\ufffdx\u003c6\ufffdÏ\ufffd|\u0002\u0007\ufffd?{\ufffd\ufffd\ufffd\ufffd۟Xj35\ufffdn\ufffd\ufffd1\ufffd\u000cR\ufffd\u0013\ufffd\ufffd^\ufffd̯XZ\ufffd\ufffd0*\ufffd\ufffdU\u0019\u000b\u0013)\u003e\ufffde\ufffd\ufffdbc\"\u0013\ufffd\ufffd\ufffdx\ufffd\u0006\ufffd\u0018`\ufffdPCc\u0017J\ufffdT\u0008{\u0008\u0018\ufffd\u0000@s\u000e0\ufffd\ufffd\u000f \u0013\ufffd\ufffd\ufffdgb\u0018̤\ufffd\u0017\ufffd\ufffd\u0001\ufffdunEn\ufffd\ufffd\\\u00266\u001d\u0026b\u0026c\u0011\ufffd\ufffd\ufffd\ufffd2\ufffd\u000c\ufffd!NPb\ufffd\ufffd\ufffd\u0001\ufffdĥ,,3e\u003c\u000c\u001a\u0008]\u001a\ufffd\ufffd\ufffd\ufffd\u0001I(]d\u00004\ufffdh\u0010\ufffd\u001d\ufffdA\ufffd\ufffdΎ\u0000c\u0017\u0005(\ufffd\ufffd\ufffdF\ufffd|\ufffd\ufffdh\ufffd\u0006\ufffd\ufffd\ufffdJ\ufffd\ufffd\ufffdI\ufffd0|\ufffd\u000b\ufffd\ufffdV\ufffdћ\ufffdd\ufffd\ufffdr\ufffdB\ufffd\\\ufffd\ufffd\u000eϊG\ufffd\ufffd\nLb\ufffd\ufffd.\u0006\ufffd_\ufffd\ufffd\u001f\ufffd\ufffd0\ufffdɂ\ufffdӐ\u0017E\u0003j\"gL\u0026à)\ufffd\u0006W==\"\ufffdR\ufffd,Vܘa\ufffd\ufffd%L\ufffdG\u00113\u001b\u001bܦb{\ufffd~.Ri\u0018~9K\ufffd\ufffdcQr+Ԃ\ufffd*\ufffd\ufffd\u0019Res1f\ufffdQɘ\ufffdSq\ufffd\ufffd\ufffd\"a\u0013]2+\ufffd\ufffd\ufffd\ufffd\u0016\ufffd\ufffdǦx\ufffd\ufffd(\ufffd\ufffd\u000b?D\ufffd`\ufffd\ufffd[\u0011\u0018\ufffd\ufffd\ufffdH!\u0015\u000e\ufffd\ufffd3\r͂DEIz\ufffd'z\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\r\ufffd\ufffdjZ\ufffd\ufffdgO\ufffdf3L7g\ufffd\ufffd\ufffdr\u001a\ufffdys\ufffd\ufffd\ufffd\ufffd\ufffd\u0018\u0006r\ufffd\ufffd6\ufffd\ufffds\ufffd5\ufffdZ{=[}p\ufffd)]skȸ\ufffd㻽\ufffdg=\u0019?\ufffd\ufffd\ufffd\ufffd\ufffd%]$ϕ\ufffd\ufffd\ufffda\ufffd\ufffd\ufffdT%8\ufffd\u000c9\u0008+\u001a\ufffdN\u001e\ufffd\ufffd\ufffdӖ\ufffd\ufffd\ufffd\ufffd\u0007\ufffd\ufffdme\ufffdB\ufffd\ufffdz90\ufffd\ufffdM\ufffd\u0013\ufffd\ufffd\ufffdk\ufffd\ufffdE]\ufffd\r}\ufffdB\ufffdu{u\ufffd\ufffd\ufffd\ufffd\ufffd;\ufffd\ufffd\u0002\ufffd\ufffd\ufffd/%\ufffd\ufffd%\ufffd\ufffd\ufffd\ufffd\ufffd{WZ\ufffd\ufffd\ufffd3ο\ufffdJO5\ufffd\u0015\ufffd;\ufffdF|\ufffd\ufffd\r(g\ufffd\ufffd\u0001\ufffdܞ\ufffd\ufffd8f\ufffd\ufffdRK\ufffd\ufffdZ9u:\u0011\ufffd\u0006K\ufffdnXQʌ\ufffd\u000b\ufffd\ufffdb\ufffd[ \ufffd\u003c\ufffd\ufffd-\ufffdˣǕ\ufffdp\n\ufffd\ufffdA\u0004\ufffd\ufffd\ufffd\u00134\ufffd\ufffdI\u0015[\ufffd\ufffd)k\ufffd\ufffd.\u0007\ufffd\u001e\ufffd\ufffdC\ufffd\t\ufffd\ufffd7\ufffd\ufffd\ufffd,c+\ufffd\u0015~|\ufffd+\u001b\ufffd\ufffd\ufffd/\ufffd\ufffd \ufffdԭu\tƨ\ufffd\ufffd+k\u0013\ufffdc]\ufffd6\ufffdqCj\ufffd\"\ufffd\ufffd\u0011+\ufffdl:\u001a\ufffd\ufffd-'ob+T}J\ufffd\ufffdu\ufffd\ufffd\ufffdտY\ufffd\t\ufffd\ufffdf\n\ufffd\u0004\ufffd\u000b{k\ufffdt[x\u0014\ufffd\ufffd\ufffdo\ufffdk\ufffd\ufffd\ufffdz\ufffdd\ufffd\u0019c\u0007\ufffd\ufffd\ufffd~]\ufffd_-X\ufffd\ufffd\ufffdP?\ufffd\ufffd\ufffd\ufffd}\ufffd\ufffdƼ\ufffd\ufffd\ufffd\ufffd\u0003F4C\ufffdJ!\ufffd\ufffd\u000e\ufffd\u001d\ufffd\ufffd\ufffd:ս\ufffd~\ufffd\ufffdv\u0026\ufffda\ufffd\ufffd\ufffdө\u00128\ufffd\ufffd\ufffd\ufffdr:\ufffdT\ufffdS\ufffd\"f\ufffd\u000b\ufffd\ufffd\ufffdW\u001aD;\u0003\ufffd\ufffd\u0010\ufffd2ܒXl\ufffdr\ufffd\u0019\u0019\u000b\ufffd%\ufffd\ufffdݛ\u000fg\u0008۫\ufffdݖ\n߄\ufffd`\ufffd\u000e9pX@\ufffd;|\ufffd\ufffd\ufffd\ufffd\u0015ǵ\ufffd\ufffd:!\ufffd]𮍑\ufffd\ufffd1\ufffd\ufffd\ufffdK=|\ufffd\ufffd\u0013^k\u001e\ufffd\u0005\ufffdj\u0008\ufffd\ufffd9\ufffd\ufffd=q`Uo8\ufffd^\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdP\ufffdP:\u0010I\u0006\ufffd\ufffd\ufffd߱͟\ufffd\ufffd7\ufffd\u003e\ufffd\ufffd\ufffd}:\ufffd37\ufffd\ufffd\ufffd!\ufffd\ufffdן\u001bX\ufffdU\ufffd!\ufffd\u001a\ufffd\ufffd\ufffdf2\u0011:\ufffd\ufffd(hv\ufffd\ufffd\u0003\ufffd\u000e;\ufffdn\ufffdؕХV\ufffde\u00152\tVh\ufffd\n\ufffdm\ufffd2\ufffd\u0008\ufffdy\u0012^R\ufffd\u0018Z\ufffd\ufffd\ufffde1\ufffdx\ufffdg\ufffd\u0003\ufffdU\ufffd\u0015\u000f\ufffd\ufffd2v\ufffdF4\ufffd\ufffd\ufffd\u000fTX\ufffdK=70\ufffdD\u000b\ufffd`\u001a\ufffdT\u00055\u000f\\\ufffd\ufffdY`\ufffd\ufffd\ufffd\ufffdۢ r\u001b\ufffd\ufffdν\ufffd\ufffdZ\ufffd%@Nj\ufffd\u0002\ufffdٴ\u001b3\u0013\ufffdBX)Q.\ufffd\ufffd\u0018Wv\u0018PC\ufffdf\"\ufffd\ufffd\u0008U]\ufffd\ufffdY\ufffd\ufffdFc`\ufffd\u0015\ufffd[\u0006\ufffd\ufffd^\ufffdk{\ufffd\ufffd+0֕\ufffd\ufffd\ufffd\ufffd\u0002\ufffdQTe\ufffd\u0004\ufffdD\ufffdc\u0001\ufffdq\ufffdq{1z\ufffdK\ufffd\r{\u0005\ufffd\ufffd\u000b\ufffdg\ufffdY\ufffds\ufffd'\ufffdo\ufffd\ufffdX\ufffdGv\ufffdS\ufffd\ufffd#\ufffdy\ufffd\u001f\ufffdߘ\u00163\ufffd\ufffdT\ufffd\ufffd2\u0004\u0003\ufffd\ufffds,d\u0010\ufffd\ufffdu\ufffd\ufffd\u0002\ufffd\ufffd\ufffd\ufffd\ufffd,\ufffdՙ\ufffd]8\ufffdC\ufffd\ufffd\ufffd\ufffd\ufffd\u0017\ufffdY\ufffd\ufffd\u0004\ufffdU\u0002\ufffdw=\ufffdF\u0008\t#;\ufffd\ufffd\ufffd\ufffd\ufffdE\ufffd]\ufffd!\ufffd/*\ufffd\ufffd=\ufffd\u0026\ufffd\ufffd]g\u0026\ufffd\ufffdk\ufffdɸR\u001ee\u003c\ufffd,\ufffd{\ufffdΐ\ufffd\ufffd\ufffd\ufffd0l.m\n\ufffd\ufffdRp%\ufffdF}\u001ank245\ufffd\ufffds*}\u0003k\ufffd\raC\r\ufffd\u00144\ufffd\ufffdV\u0013\ufffdU\u0012\ufffdk\ufffd\ufffd\u001ci\ufffd\ufffdÑ\ufffdY\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdo\ufffd\ufffdQ\u001e൞\ufffd\ufffd/\ufffd\ufffd\u0016\ufffd2\ufffdgc\ufffd\ufffd\ufffd\ufffd}6\ufffd\t=\ufffd\ufffdk◶\ufffd\ufffd\ufffd\ufffdЙ̷\u0016\ufffd\ufffdnQ\ufffd\u003c\ufffd:\u0007\ufffd\u001c\u0015\ufffde̚}d)L]\ufffdx\ufffd5G\u003c\ufffd\ufffdT\u0005\ufffd5I\ufffd\ufffd\ufffd\ufffd \ufffd\ufffd0?F\u0011\u0019\ufffd\ufffd\ufffd\ufffd\ufffd\u000f\ufffdFc8\ufffd\ufffd\t\ufffd(\ufffd\u0011\r\ufffdN\ufffd\u001c\u0015U(f\\U܊\ufffd\ufffdЋp\ufffd\ufffd\u001dֺ\u0019\ufffd\n\ufffdR\n.\ufffd:kK\ufffdz\ufffdTa\ufffd\ufffd\u0013l\ufffd\ufffd\u0007\ufffd\ufffd\u0011\u001b\ufffd~\ufffdh\ufffd\ufffd\u000bͪ\u001d\ufffd;\ufffd\ufffdT\ufffd\u003e\ufffd\ufffd\ufffd)9\u0011\ufffdG(̗w\ufffdk6}v\ufffd\ufffd\u003e\ufffd,\ufffd\ufffdQ\ufffd\ufffds\ufffd3\ufffdV\u0019\ufffd\ufffdX\ufffd\ufffd\ufffd\ufffd\ufffd`D\ufffd\u003c(]\ufffd\u0018ꆪ\ufffd$\ufffd\ufffd{\u0008\ufffd\ufffd~c\ufffdc\u0014_\ufffdk\ufffd\ufffd_\ufffd\ufffd\u0014q\u000e\ufffd\ufffd\ufffd\ufffd\ufffd1\u000e\u0003_\ufffd\u001f\ufffd6uP\ufffd\ufffd\ufffdR\ufffdW\ufffd3\ufffd\ufffd\\\ufffd*\ufffd\ufffdLE\ufffd{\ufffd\ufffd\u0026\ufffd\u0000\ufffd#~\u0016\ufffd\ufffd\ufffd\ufffdޤ\ufffd\ufffd\ufffd?\ufffdA\ufffd7K\ufffd\ufffd*0!ZkL\ufffd[\ufffd\ufffde\ufffdj\ufffd\ufffd)\ufffd\ufffd䦴\u000fÊ\ufffd\ufffd\ufffd\ufffdٟ\ufffd(\ufffdyL,\ufffd\u0026P\ufffdt$\u0017s\ufffd\ufffd\ufffdH\ufffd\ufffd5\u0002\ufffd\ufffd\ufffd\ufffd\u0015\ufffd\ufffd6NE^\ufffd/\ufffd\rYEi\ufffd۶a\\\u001e\ufffdA6\u00033\ufffd\ufffd8\rFo1s\ufffdgػzԫ\ufffd{уmre4\ufffd\"\ufffd\ufffd\ufffd9\ufffd\u0004p\ufffdr\u0016i\ufffdG\u0026\ufffd\u000b\\\u0011\ufffd\ufffd\ufffdY\ufffd\ufffd\ufffd\ufffd\ufffd\u000e\ufffd-\ufffdG\ufffd\ufffd\ufffd8\ufffdH\ufffdIDD\ufffd\ufffd:\ufffd\ufffd\ufffd'\ufffd\ufffde¡\u003e\ufffd\ufffd\ufffdn=\u001c7\ufffd`\ufffdDkx\ufffd=\ufffd\ufffdf+\ufffdo\ufffd\ufffd\u0005ن\ufffdĄzy{\ufffd~\ufffd\ufffd\ufffd.$\ufffdk\ufffd\ufffd\ufffd\ufffd\ufffdP\ufffd\ufffde\ufffd\ufffdKR\ufffdV\ufffd\ufffdlL\ufffd\ufffd\u0018\ufffd\ufffd\ufffdpu\ufffd{@\u0014\ufffd_\ufffd\u0004\u0018\ufffdwΉ̑\ufffd\ufffd\u0018\ufffd\ufffd\ufffd\ufffdCy\ufffd\ufffd\ufffdԩ\u0008\ufffd\ufffd\ufffd\u001c#6\ufffd\ufffd\ufffd\ufffdXUFΜ\ufffd\u001ar\u000c\u0004Hq\ufffd6\u00116XG\u0026]Rl6\u0002o\ufffdV\n*\\\ufffd\ufffd\ufffdw\ufffdL\ufffdg\ufffd\ufffd4\ufffd\ufffd{\ufffd\ufffd\ufffd+b\ufffdo\ufffd/M\ufffd\ufffd2\ufffd\ufffd\ufffd\ufffd\u001a{\ufffd7\ufffda}\ufffd\u0005,ᖇ\ufffd@y\ufffdE\ufffd\ufffd\ufffdC\ufffd\ufffd@\ufffdE{\u0003\ufffd\ufffd\ufffd˼p\ufffdΧJ\ufffd\ufffd\ufffd)\u0013\u0017\u0019GU\ufffd\ufffdh\ufffd\ufffd\ufffdB\ufffd\ufffdRR\ufffd\ufffd\ufffd'7\ufffd\ufffd\u0010\ufffd\u001e\ufffd+i\u0005\u001f\ufffd\u0012Y%\ufffdj\ufffd\ufffdDl\ufffdI\ufffd(\ufffd\ufffdK\u003e\ufffd\ufffdT.\ufffd/\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0017\ufffd\ufffdw\ufffd\ufffd2~\ufffd\ufffd\ufffd\ufffd\ufffd'\ufffdt\ufffdsU_?mt\ufffdݱ`I\u0026\ufffd\ufffd|əA\ufffd\ufffd?\ufffd\ufffd\ufffdH\ufffd\ufffd\u0013\u0018\ufffdD\ufffd\ufffdЏ\u001c\ufffd^\ufffd\ufffd\u001d\ufffdY[\ufffd0F\u003c\u0014\ufffd\ufffdN\ufffd\ufffd\ufffd˯\ufffd\ufffd\ufffd\u000e\ufffd3\ufffd\ufffd]Xcn\u001f\ufffdz\u000f\ufffd2\ufffdt.=ӓ\ufffd\ufffd\u000f\ufffd\ufffd\ufffdN\u0018y\ufffd\ufffdM\u0011\ufffd\ufffd79\u000f\ufffd^l5\ufffd\ufffd\ufffdpV˦XP\ufffd\u0012z8\ufffd\ufffd3\ufffd\ufffd\"\ufffd\ufffd\ufffd=\ufffdr`k\t,A\ufffd`WBX!\ufffd\ufffdm\ufffd`\ufffd\u001f\ufffd\u0019\u0012Y\ufffdMC\ufffd\u0000T\ufffd$\ufffd\ufffd\ufffd\n6x֘b\ufffd\ufffd\ufffds\ufffd['\u001d\ufffd\ufffd\u0018=FY\ufffd@V\ufffd\ufffd\ufffd\u001fO\ufffd\ufffd\ufffd\ufffd\ufffdRt\ufffd\ufffd \ufffd\ufffd\ufffdU'\ufffdґW\ufffd1\ufffdB\ufffd\ufffd\ufffd֗\ufffdM\u000eFqo\ufffd\ufffd\u003e\ufffdXE=\ufffd\ufffd\ufffd\ufffd~\ufffd0\ufffdF.辱\ufffd\ufffd|\u00020\ufffd\ufffd?\ufffd\ufffduƚ9\ufffdK\ufffd\ufffd;Pv\ufffd\ufffd\ufffd\u000ejkJw^H\ufffd\ufffdsZ\u0013\u0012P??\"-y\ufffd\ufffd\ufffd?t\tSo3:\ufffdD_\u001e\ufffd\ufffd?ļo\u001f\ufffd|y\ufffd\ufffd}\ufffd\ufffd:{\ufffd\u0015p{\ufffd\ufffdq\ufffd\ufffd\ufffd\ufffd#;\ufffdF\ufffd\ufffdT\ufffd}\ufffdvz\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd;\ufffdF\ufffd\u0019\ufffd\u0000j\ufffd\ufffd\ufffd\ufffd+\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2591\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:19 GMT\r\nSet-Cookie: AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:25+05:30","url":"https://ginandjuice.shop/my-account","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB","Referer":"https://ginandjuice.shop/about","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/my-account","scheme":"https"},"raw":"GET /my-account HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB\r\nReferer: https://ginandjuice.shop/about\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"0","Date":"Thu, 07 Sep 2023 15:34:26 GMT","Location":"/login","Set-Cookie":"AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/, AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"raw":"HTTP/1.1 302 Found\r\nConnection: close\r\nContent-Encoding: gzip\r\nDate: Thu, 07 Sep 2023 15:34:26 GMT\r\nLocation: /login\r\nSet-Cookie: AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\nContent-Length: 0\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:26+05:30","url":"https://ginandjuice.shop/login","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm","Referer":"https://ginandjuice.shop/about","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/login","scheme":"https"},"raw":"GET /login HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm\r\nReferer: https://ginandjuice.shop/about\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"1793","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:26 GMT","Set-Cookie":"AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/, AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdY\ufffdn\ufffd6\u0014\ufffd\ufffd\ufffd`5\ufffdi\ufffd*\ufffd\ufffd^6\ufffd2\ufffd\ufffdm\ufffd\"M\ufffd\ufffdٚ\ufffd)(\ufffd\ufffd\ufffdR\ufffd\u0026Rv\ufffdH{\ufffd=\ufffd\u000eIY\ufffdmɒ{\u0001\ufffd\ufffdA\u0010\ufffd\ufffd\ufffd\u001f\u000f\u000fϕ\ufffd\ufffd{uq\ufffd\ufffd\ufffd\ufffd5JTʦ?L\ufffd\u0007\ufffd\ufffdIBpd\ufffd\ufffd!\ufffd\ufffd\u0013Jr2\ufffd\ufffd\ufffdHQ\ufffd!\ufffd\u001eÁf#\ufffd\u0017J\ufffd\ufffd\u0010\ufffdw\ufffdWC8\u0004\u0002\ufffd\t\ufffd\ufffd*\u0019\ufffd\t!\ufffd\u000fLC\u0000\ufffd\ufffd\u0002\ufffd\ufffd\ufffd\u0006H\ufffd\ufffd\ufffd\ufffdΜ\ufffdE\u0026r\ufffd\ufffdPpE\ufffd\ufffd\ufffd\u0005\ufffdT\ufffdGdNC\ufffd\ufffd\ufffd#TH\ufffd\ufffd !\ufffd\ufffd\ufffdυ\ufffd@\ufffdaN3\ufffdd\u001e\ufffdNC\ufffd[\t\u0003\u001c\ufffdC@\"Ld)\ufffd\u001f\ufffdJg:\ufffd\ufffd\ufffd\ufffd\u0010n$\ufffd=`T\ufffd\ufffd\ufffd\u0014\ufffdS\ufffd-\ufffdcKu\ufffd\ufffd1\ufffd\u000b\ufffd\ufffd\ufffd#\ufffd\ufffd\ufffd\ufffd\u000bUQ\ufffd\ufffd\ufffdLĔ#\u0017\ufffd\ufffd\ufffd\ufffd8\ufffd^\ufffd\ufffd\u0005(\u0008]%\"\ufffdx\ufffd\ufffd^\ufffd\ufffd\ufffd\ufffdI \ufffd\u0012\ufffd\ufffd\ufffdY\ufffd@\ufffd\ufffd\u001c\ufffd\ufffdw\ufffdW\ufffdШ=\u000b\t\u0015\u0015\u001c\ufffd\u000cK\ufffd;\ufffdP܈X\ufffd`fc\ufffdY\ufffdm\ufffd\ufffd\ufffd\ufffd\ufffdJ\u0004\ufffd\u0018E\ufffdр\ufffdX\u0011V\ufffdy\ufffd8|\ufffd\u001bE\u000b\u0012 \ufffd\ufffd\ufffd\u0010\ufffd]a\u001f\u001as\u0012\ufffd\ufffdȑ\"RQ\u001ek\ufffd\ufffd\u003c\ufffdً\ufffdBʨ*-I\u001f\u0005h\ufffd\ufffdU\ufffd\ufffd\ufffdp}\u0014m\ufffd\u000e\u0012\u003c\u0015`Up\ufffd$\ufffd6\ufffd#\ufffd8\ufffd\ufffd\ufffdn\ufffd|4\ufffd\ufffd\ufffd|}\ufffdhm6\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd.\ufffd\ufffdʛ\ufffdO\ufffd\u0016\u0017\ufffd\ufffd\ufffd\u0019z\ufffd\ufffd\ufffd\ufffd}\ufffd\ufffdkm8^\u001f\u003ey\ufffd\ufffd\ufffd\ufffd\ufffd#\ufffdD\ufffd\ufffd\ufffdl\ufffd\ufffd(8}\ufffd\ufffd\ufffd\r\ufffd\ufffde\ufffd)c\ufffd\ufffd\ufffd\u000f\u001eֻ2\ufffd\ufffd\ufffdp\u000fD\ufffd\u0006\ufffd\ufffd\u0005ڢ\u001d\ufffd\ufffd\ufffd-\ufffd'\ufffd\ufffd\ufffdP\ufffd\u0004\ufffdF\ufffd= %\ufffdJ\ufffd\ufffd\ufffd{`Ӡ\ufffd\u0016s\ufffd6\ufffd\u0005\u0018\ufffd\ufffd\ufffd\ufffd\ufffd\u0003k\ufffd/\ufffdF\u0007-\ufffdڼ-\ufffd\ufffd)\ufffd\u0002[\ufffd]N\ufffd\rc\u0007^\u000bL\u000b\ufffd4\ufffd\ufffde\"\u0016\ufffd\ufffd@\ufffd\ufffd\ufffdp\ufffd\ufffd\n\ufffd8\ufffd\ufffd#\ufffdY\ufffdq\ufffd65G\ufffd\ufffd\ufffd\r\ufffd466\ufffdV\u000e\ufffd\ufffd]\ufffd,\ufffd)\ufffdK;jq\ufffd-HF\ufffd\ufffd\u000c#^n\u001d\u0014JAP\ufffd\nu\u003c\ufffdL\u000cz\u0002\ufffd\ufffdET\ufffdJv*e\r\ufffd\u001b\ufffd\ufffd\ufffdK\u0017\u0018\ufffd^\ufffd\ufffd\ufffd\ufffdX8\u0010\ufffdr\ufffd\u0017ڏ\ufffd\ufffd˯$\ufffd\ufffd+\ufffd\u0017\ufffd\u0010\ufffd\ufffd\ufffd\ufffd76\"\u001c\ufffd\ufffd\ufffdʥ\ufffd\u0017\ufffd\ufffd*T$\ufffdՓ\ufffdn\ufffd\u0000yR\ufffd\ufffd\ufffd\u0004\u0002\ufffdrr\ufffdW\ufffd\ufffdc\ufffd\u0001\u0018K\ufffdC\ufffd\ufffd\ufffd\t\ufffd\u0013-,\ufffd\ufffdV\ufffd\u000c\ufffds\ufffd\ufffda\ufffd{\ufffd[\u000f\u001bv\ufffd\u003c8\ufffd\u0001\ufffd\ufffd\ufffd\u0012\u001b\ufffdA\ufffd\ufffdm\ufffd~W\ufffd\ufffd\ufffd7\u0012\ufffd\ufffdȇ\ufffd}\ufffd\ufffd\ufffd8\ufffd,k#\u0002zzF۫N\ufffd\ufffd*\u0015\u000e\ufffd^\ufffdԘ\ufffd\n}\ufffd\u001d|\ufffdF\u0004p\ufffd\ufffdcF`7\ufffdF\ufffdi\u001c\ufffdRў\ufffd\u003eL\ufffd\u0000\ufffd\ufffd\ufffd\ufffd4\ufffd:\u0013`G\nn!\ufffd\u0014\u0016\u001b\\\u00265CŢ{\u0012\ufffdK\ufffd\ufffdz8\ufffd\ufffd]g\ufffd\ufffdRx\ufffdꝚ\u000fj`7\ufffd\ufffd\ufffd\u0008\ufffd\u0013\u001bpk\ufffd\u000bEgUA\\\ufffd⮅\ufffd\ufffd\ufffd\ufffd\u001d\ufffd\ufffd\u0001\ufffdF\u001d,\ufffdu\ufffd\u001a\u0007\u0014\ufffdi%\ufffdяk\u0008\ufffd\ufffd%\"\ufffd//\ufffdރk\u00080\ufffd6\ufffdm\ufffdQ\ufffdA͘\ufffd\ufffd\u000b\ufffdC\ufffdo{\ufffd\ufffdF\u0011\u0001\u0017\ufffd\ufffda(\ufffd\ufffd\ufffd\ufffd\ufffd\u001508M\ufffd\u000fD\ufffd\ufffd8\u003e\ufffd\ufffd\\\ufffdQ\ufffd\ufffd\ufffd\u0014o.\ufffd\ufffd\ufffd\ufffd\ufffdϮ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u001ad0\ufffd\ufffdNR\ufffdO\ufffd`\ufffd\ufffd\ufffd\ufffd\ufffd]\ufffdi\ufffd\ufffd\n\ufffd\u000e\u001e\ufffd;\ufffd1\ufffd\u0018\u000eI\"X\ufffd;\ufffd%\ufffd\ufffdq\ufffd\ufffd\ufffdW'*j6\\(1\u0013a!{\ufffd\ufffd!\ufffdR|50Ȳ\u0008R\ufffd\ufffd\ufffd\ufffd\u00030\u0001;\ufffdˑ\ufffd\ufffdu\u0019P\ufffd\u0001\u000c\ufffd\u0026Cn\ufffd\ufffdL\u0008\ufffd\ufffd\ufffdE\u000eM]\ufffdl\ufffd\ufffd]\ufffde\ufffd\ufffdȌ\ufffdtT\ufffdQ\ufffdo\ufffd\tM\ufffd\ufffd\ufffdP\u0026\ufffd\ufffd\ufffd\u001c\ufffd\ufffd\u001c\ufffdT\ufffdv\u00153\ufffd\ufffdA\ufffd\r\ufffd\u001a\ufffd\u0002\ufffd\ufffdh\u0007D6}+\ufffd\u003et/\ufffd\u00109\ufffd\u00164\"(\u0014iZphV\ufffd\ufffd!\ufffd\ufffdc\u001e\ufffd[ӿ\ufffd0\u0007\u000f\u000f\ufffd#\ufffd꒻\ufffd\u0015\ufffd\ufffda'\ufffd\ufffdд\u0019 \ufffdu\u001fe\ufffd\u0001\u003e\ufffd\ufffdO+\u0004K\u0002\ufffdP0\u0006ucn\ufffd\u0004\ufffd5r*rr\ufffd\ufffd]\ufffd\ufffd\ufffd\ufffdq\ufffd\ufffd\u0026Q\u001f\ufffd\u001c\ufffd\ufffd[y\ufffd\ufffd$Ѫk\ufffd\ufffd\ufffd\\\ufffd\ufffdT\ufffd\ufffd\u000e\ufffd \u0017\ufffdU\ufffdp2!UE\ufffdC\ufffd2Y\ufffd\u0000\u003e#|\u0010\u0008\ufffd\ufffdz\ufffd\ufffd\ufffd梯\r\tG\u0011\ufffd\ufffd\ufffd|\ufffd;:\ufffd\ufffd_\u0018~)f\u001f\ufffd\ufffd\ufffd\ufffd\ufffdwrL\ufffd]\ufffda\ufffd6\u003e\u001e/\ufffd\ufffdiܷߚ\ufffd\ufffduT\ufffdѯ\ufffd\ufffd\u0019\ufffd\ufffd\ufffd^Qݖֵ\ufffd0\ufffd\ufffd2\ufffd.\ufffd\ufffd'\ufffdZJ3\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd!\ufffd]\ufffd.\ufffd)x\ufffdh\ufffd\ufffd\"\ufffd\u0018\ufffdWF\ufffd\u0007\ufffd\ufffd\\=Ԟ\ufffdw\ufffd\u001dbR\u003e\u0013\u0003d4\ufffd!E\u001e=\ufffd\t\ufffdt\ufffdt$(UByܝ9\u0007\ufffd\ufffd\ufffd\ufffdnx͛\ufffd\ufffd\ufffd\ufffdJ\ufffd8\ufffdZ\u001cgj\u0015\u00031HA\ufffd(x\u0004R\ufffd\ufffd\ufffd\ufffdn`\t]\ufffd֗P\u000b\rS\ufffd\u0017\u0003T\ufffd\ufffdg\ufffdȚn\u001aV\u0007\ufffd\ufffdD.D\ufffdOΆ\ufffd\u001aS\ufffdr\ufffd]\ufffd~k\ufffd=X\ufffd\ufffdq\ufffd\ufffd\u0012\ufffd?*\ufffd\ufffd^\ufffd^\ufffd\ufffd,\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0013(\ufffdQ@ \ufffd\u0011\u0013U4\ufffdp\ufffd\\?\ufffd{\u0008\ufffd繞u\ufffdA\ufffdUuփ\ufffd\ufffd\ufffd=\ufffd3\ufffd\ufffdR\ufffd\"/\u0017\ufffd\ufffd7\ufffd]\ufffd\ufffd\ufffd\u0001\ufffd\ufffd\ufffd\ufffd\ufffd\u001e\u001f\ufffd\ufffd% \\Y\u0004t\ufffd\ufffd\ufffd\u0001'ۯ\ufffd\ufffd8mu\ufffd\ufffd֠\ufffd\ufffdi-H\ufffd\ufffd\ufffd=ʒ\ufffd\ufffd\ufffddW\ufffd\ufffdX\u0026E\u00087z\ufffd\ufffd\ufffdҧ\ufffd\ufffdO\ufffd__\ufffd\ufffdO\ufffd\ufffd\ufffd\ufffd{\ufffd\ufffd\u0016a\ufffd\ufffd\ufffd\u0018v\u0014\ufffdַLmj\ufffd\ufffd\ufffd]\ufffdB2\u0012Q9\ufffd\u0001R\ufffd\ufffd\u001f\ufffd}9\ufffdo\u001b\u001d\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 1793\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:26 GMT\r\nSet-Cookie: AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:30+05:30","url":"https://ginandjuice.shop/login","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Content-Length":"53","Content-Type":"application/x-www-form-urlencoded","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/login","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/login","scheme":"https"},"body":"csrf=GhyXet8wACfYUo1chNYuFPbOCI36SVSj\u0026username=carlos","raw":"POST /login HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nContent-Length: 53\r\nContent-Type: application/x-www-form-urlencoded\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/login\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"1877","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:30 GMT","Set-Cookie":"AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/, AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdY\ufffdn\ufffd6\u0014\ufffdߧ\ufffd4\ufffdN\ufffdʪ\ufffdl\ufffd\u0010\ufffd\ufffdzK/i\u00134-\ufffd\ufffdOA\ufffd\ufffdŔ\"U\ufffd\ufffd\ufffdG\ufffdk\ufffd\ufffdvHʊlK\ufffd\ufffd\u000b\ufffd\u001f5\ufffd\ufffd\u003c\u003c\ufffdxxx\ufffd\ufffd\ufffd\ufffd'\ufffd\ufffd\ufffd\ufffd}\ufffd\u0014E:\ufffd\ufffd{#\ufffd\u0007\ufffdg\u0014QL\ufffdW;\ufffdL|FQJg\ufffd \ufffdJfiHU\ufffd\ufffd\ufffd4\u0008\ufffd\nT\ufffd\ufffdk\ufffd\ufffd\u0012\ufffd@@)\ufffdc\ufffdsNUD\ufffdn\u00033\u0010\u0000\ufffd\ufffd\u0001\u0026\ufffd\ufffd\u0001b\ufffd1\u00128\ufffdco\ufffd\ufffd2\ufffd\ufffd\ufffdP(\ufffd\ufffdB\ufffd\ufffd%#:\u001a\u0013\ufffd`!\ufffd\ufffd\ufffd\u0001\ufffd\u0014M}\ufffd\u0010v\ufffdt,\ufffdWASa\ufffd\u0012\ufffdT\u001a\ufffd\ufffd\ufffd@7\n\u00068\ufffd}@\ufffd\\\u00261\ufffd\ufffdo\ufffd7\u0019\u0005nEw\u0008\ufffd\ufffdx\u000f\u0018\ufffd'p2Moup\ufffd\u0017\ufffdQ\ufffd:t,\ufffd\u0019\ufffd駁\ufffd\ufffd4\ufffdj\ufffd9\ufffd\\\ufffd9\u0013\ufffdG\ufffd\ufffd\ufffd\u003e\ufffd\ufffd3\ufffd2\u0003\u0005\ufffd\ufffdH\u0026\ufffd\ufffd\ufffd\ufffd\ufffd\u000f\ufffd\ufffd~4\ufffd$Gb\ufffd\ufffd$\ufffd \u0012\ufffd@\ufffd\ufffd\ufffd\ufffd\ufffdW4\ufffd\ufffdBCͤ@!\ufffdJ\ufffd=g(\u003e\ufffdN\u003c\ufffd\ufffdX`\u0017%\ufffd4\ufffdy\u00171\ufffd\ufffd\u0007#B9\ufffd\ufffd\u0014k\ufffds\ufffdȸ\ufffd\ufffdp\ufffdhI\ufffd\u0008d\ufffd,\ufffdvW؇\ufffd\u0005%h\u0026S\ufffd\ufffd\ufffdL\ufffd\r\ufffd}1U\ufffd\ufffdj!\ufffdL\ufffd\ufffdd\ufffd\u00024կ\u0015`\ufffd\u0012\\\u001eŘ\ufffd\ufffd\ufffd\ufffd%X\u0015\ufffd\u0026M\ufffd\ufffd\t\"\ufffd\ufffdO\ufffdn\u0007h\ufffd\u0006\ufffd\ufffd\\\ufffd\ufffd\u000e\ufffdfc\ufffd\ufffd\ufffd\u001e\ufffd\ufffd\ufffd4PEu\ufffdxmq\ufffd\ufffd=6C\u0007խ\ufffd\ufffdGսֆG\ufffd\ufffd\ufffdC\ufffde\ufffd\ufffd1\ufffdZN\u000fz\u0017G\u001f\u0007\ufffd\ufffd\ufffdy\u0018?c\ufffd\ufffdmB\ufffd9\u000f\ufffd\ufffdq\ufffd\ufffdܕSld\ufffd{\ufffd\ufffdV\ufffd\u0006gh\ufffd6\ufffd\ufffd\u001d\ufffdЎ\ufffd\ufffdU\ufffdQ\u000ev\ufffd\ufffd{@Z\"\u001dQ{u\ufffd\ufffdM\ufffd\ufffdk\ufffd%ذ\u0017`tV\ufffd\ufffd\u0016{\ufffdl\u001fٍz5\ufffdƼ\u001d\ufffd\ufffd)\ufffd\u0001[\ufffd]\ufffd\ufffd\u000bc\ufffd\ufffd\u0006\ufffd\u0006N\ufffd\ufffd\ufffds9\ufffd\ufffd\ufffd@o\ufffd\rp\ufffd\ufffd\u001d\ufffduX\ufffdE\u003e\ufffdF\ufffd\ufffdmJ\ufffd\ufffd\ufffdL\u001bx\ufffd\ufffdڄ_8\ufffd1v\ufffd\ufffd\ufffd\ufffd8\ufffdݨ\ufffdq\ufffd 9kg\ufffd\ufffdx\ufffd\ufffd4\ufffd\u001a\ufffd\ufffdS\ufffd\u0017\ufffdeb\ufffd\u0013XF*I\u0016jը\ufffd5\ufffd\ufffd\ufffd\ufffd\ufffd.\ufffdԊ\ufffd\u0008~\ufffd\ufffd\ufffd\ufffdS\ufffdiori\ufffdH\ufffd4\ufffdN\ufffd\ufffd\ufffd\t\ufffd\u000fJA~\ufffd\u0011\ufffd0\ufffd\ufffd\ufffd\u003e\ufffd\ufffd\ufffd\tV\ufffd\ufffd\ufffdTO\ufffd\ufffd\u0005\u0003\ufffdI\ufffd\ufffdOF\u0010\ufffdV\ufffdwy\u0015L}n\ufffd\u0000Ɗ\ufffd\u000fܿV\ufffdM\ufffd\ufffd\ufffd\u0026\ufffd\u001a\ufffd.z.\ufffdԍq\ufffd\ufffdo\ufffda\ufffd.\ufffd\u0007\ufffd:\u00001\ufffdYb\ufffd\ufffd\ufffd|\ufffdدsT|\ufffdA\ufffd\ufffd\u001byw\ufffd\ufffd\ufffd\ufffd\u0010\ufffd\ufffdemD\ufffd\ufffd\ufffd\u0018{5)pP\ufffd\ufffdk\ufffdZӽC\ufffd\ufffdn;\u001f\ufffd\u0012\u0001|-\ufffdsNa7\ufffdFM\ufffd|nJEw\ufffd\ufffd0\ufffd\u0003\ufffd\ufffd[\ufffd\ufffd(hL\ufffd\r)\ufffd\ufffd\\SXlp\ufffd\ufffd\u000c\u0015\ufffd\ufffdI\ufffd)\ufffd[\ufffd\ufffd\u0018\ufffdv\ufffd\ufffd\ufffdJ\ufffd\ufffdT\ufffd\ufffd|P\u0003\ufffd\t\\oC\ufffd\u001c\ufffd\ufffd[j\\j6+\n\ufffd\"\u00167-\u000c\ufffdt\u0013\ufffd\ufffdu\u000f\ufffd6h`\ufffd\ufffd\ufffd\ufffd8\ufffd\u0010\ufffd\u000bɬ~|K\ufffdN.\ufffdd|uy\ufffd\u000e\ufffd\ufffd@\ufffd1\ufffd\ufffdo\u000b\ufffd\ufffd\u0004jƔ~\ufffdX\nu\ufffd\ufffd\ufffd\"F\u0008\u0005\u0017r\ufffda\ufffdҙ\ufffd\u0016\ufffdg0\ufffd8%ǯ\u0014;_\u003c[\ufffd\ufffd\ufffd\ufffd,\u000c\ufffd/\ufffd\ufffd\ufffd\ufffd\ufffd/NO\ufffd̻\ufffd\ufffd\ufffdi\u0010\ufffdގ\u001f\ufffd\ufffd\ufffd\ufffdF\ufffd\u0013Sῇ\ufffd\ufffdl\ufffdmH\ufffd\u0004\ufffd\ufffdRV\ufffd\ufffd0@+\u0011WĶ\ufffd7\ufffd\ufffd\ufffd\ufffd\u0002\ufffdh\ufffd\te}\u000f\u001c\ufffdK\ufffd;k]Hd\ufffd\ufffd\ufffdtN\ufffdSN\ufffd\ufffdG\ufffd\u000br\ufffd[\u0013\ufffdwط\ufffd\u0004\ufffd\u0015}7\ufffdv;Z\u003c\ufffd\ufffd\ufffd\ufffd\u0015\ufffd\ufffdR\ufffdd\u0014L'(\ufffdLo5l\ufffd\ufffd\ufffd\u0000v\ufffd\ufffd\nv\ufffdv\ufffd\ufffd^\\FR\ufffd\ufffdL\ufffd\u0019hD\ufffdl\ufffd\u0012Va\ufffd\ufffd\ufffd\"\ufffdl\u001a3=A.ۂ\u001a\ufffd\ufffd\ufffd0e\ufffd\ufffd\ufffd=\u001bݫ[\ufffd\ufffd\ufffdJ@\ufffdI\t\ufffd\ufffd\ufffd)\ufffd\ufffd\ufffd\ufffdg#Z\ufffd\u0005M\ufffdc\u001b\ufffdә\ufffdY;Z\ufffd\ufffd5\ufffdm\ufffdw8\ufffd(\u001aN\ufffdPh\ufffdQ̔{\u000c\ufffd\u001c\ufffd\u0008tm\ufffdlJ\ufffd\ufffdK\ufffdQ\ufffd\u001d\u0010\ufffd䥄\ufffd0\ufffd\u0026\ufffd4'KF(\ne\u001cg\ufffd\ufffd\u001c\ufffd\u00192\ufffd$X\u0010tc_GfX@\ufffd\u000c\ufffd\u0003\ufffd\ufffd@oC\ufffd)\ufffd\ufffd\ufffd\ufffdRAKl\ufffd86]\ufffd\ufffd\u0006\ufffd`\ufffd\u003c\\Q\ufffd(\ufffdB\ufffd9T\ufffd\ufffd\r\ufffd06ȱLi\ufffdw]W\ufffd\ufffd{Ro\ufffdŧ\u003c\ufffd\u001b\ufffd\ufffdoT\ufffd\ufffd\ufffd\ufffdV\ufffd\ufffd\ufffdf\ufffdֻS}\u001a{\ufffd@\ufffd\ufffd\u0017\ufffd\ufffdK\ufffd\ufffd\u0005\ufffd\u000cȫR\ufffd\u000e\ufffd+\ufffd3\ufffd\ufffdǝ\ufffd\ufffd\ufffdk.\ufffdԒ0!pڶ\ufffd`\ufffd\ufffd|\ufffds\ufffd\ufffd\ufffd\u0017\ufffd$y{\ufffd\ufffd\ufffd\u0026\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd'o\ufffd\ufffd[s\ufffd\ufffdJ\ufffd:\ufffd\ufffdJ3]\u003c\ufffd\ufffd+\ufffd\ufffd2\ufffd\ufffd\u0017\u0006\ufffdn\u0002\ufffd\ufffdQڄ\\+\u0018\ufffd\ufffdf\ufffdn9a\u00089\ufffd\ufffd\ufffdsJ\u0011r\u0016~.ȏ-\ufffd\u0013+\ufffd\u0001x\ufffdЇ\ufffd\ufffd\ufffdN\ufffdCL\u0026f\ufffd\ufffd\ufffdv5\u0014 Ç\ufffd\ufffd\ufffdΐ\ufffd\u0004\ufffd\ufffd\ufffd\ufffd7\ufffd%\ufffdv7\ufffdx\ufffd\ufffd\":*\ufffd\u0026\ufffd\ufffdӣ\ufffdp\ufffd\ufffdS\u000c\ufffd \r\ufffd\"\u0013\ufffdâ\ufffd=`\ufffd\u001bXA\u0017\ufffd\ufffd%\ufffdB\ufffd\ufffd\ufffd\ufffd\u0000\ufffdm\ufffd\u00157\ufffd\ufffd\ufffd\ufffd\ufffd\u0001(\ufffdć\ufffd\ufffd\ufffd\ufffd\ufffdYe\n\u0015n\ufffd+\ufffdo\ufffd\ufffd\u0007k2\ufffd+Ix\u000e\ufffd5S\ufffdmo\ufffd\u0006\ufffd[\ufffdy\ufffdN\ufffd\ufffd\ufffdchoДB4\ufffd6\ufffd\u0018\ufffd\ufffd\ufffddN\ufffdݱv\u000f\ufffd\ufffd\u003c7p\ufffd\ufffd\ufffd)\ufffd\ufffdzpu\ufffd\u0015Em\u0026X]j\ufffdߡ\ufffd\ufffd\ufffd\ufffdo\ufffd\ufffd\ufffdo\u0000\u0026\ufffd\ufffd\ufffd\ufffd\u000f\ufffdG\ufffd\n\u0010\ufffd\u001d\u0002\ufffdФ\ufffd\ufffdd\ufffd\ufffdP\r\ufffd-N\ufffd\ufffdx5\ufffd9\ufffd\u0005\t\ufffd~\ufffdGY\ufffd\ufffd|\ufffd\ufffdUeT\ufffd)\u0019^\ufffd\ufffdW\ufffd)\ufffd\ufffd\u0003\ufffd\ufffd\ufffd\ufffd\ufffd\u0003luv\ufffd\u0017\ufffd\ufffd\u0016a\ufffdIcc\ufffdP\ufffd:߲\ufffd\ufffd\ufffd\ufffd\ufffdvU\n\ufffdH\ufffd|r\u000fR\ufffd\ufffd\u000f\ufffdZ\ufffd\ufffday\u001e\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 1877\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:30 GMT\r\nSet-Cookie: AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:33+05:30","url":"https://ginandjuice.shop/login","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Content-Length":"70","Content-Type":"application/x-www-form-urlencoded","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/login","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/login","scheme":"https"},"body":"csrf=gW8d4KsiGvFwrnUHucc4OumZ28lv879y\u0026username=carlos\u0026password=hunter2","raw":"POST /login HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nContent-Length: 70\r\nContent-Type: application/x-www-form-urlencoded\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/login\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"0","Date":"Thu, 07 Sep 2023 15:34:33 GMT","Location":"/my-account","Set-Cookie":"AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/, AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/; SameSite=None; Secure, session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; Secure; HttpOnly; SameSite=None","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"raw":"HTTP/1.1 302 Found\r\nConnection: close\r\nContent-Length: 0\r\nContent-Encoding: gzip\r\nDate: Thu, 07 Sep 2023 15:34:33 GMT\r\nLocation: /my-account\r\nSet-Cookie: AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/; SameSite=None; Secure\r\nSet-Cookie: session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; Secure; HttpOnly; SameSite=None\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:33+05:30","url":"https://ginandjuice.shop/my-account","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2","Referer":"https://ginandjuice.shop/login","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/my-account","scheme":"https"},"raw":"GET /my-account HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2\r\nReferer: https://ginandjuice.shop/login\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"no-cache","Content-Encoding":"gzip","Content-Length":"2092","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:34 GMT","Set-Cookie":"AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/, AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd[\ufffdr\ufffd6\u0012\ufffdާ@ywq2\u0013\ufffd\ufffdc[\ufffdD\ufffdL\ufffd\ufffdɤn\ufffd\u0019g\ufffdi\ufffdd \u0010\u0012a\ufffd\u0000\ufffd\u0000%\ufffd\u003e\ufffdk\ufffd#\ufffd5\ufffdQ\ufffdIn\u0001P2%\ufffd\u0012\u0019\ufffdN?ȓ\ufffdE`\ufffd\ufffdb\ufffd\ufffd\ufffdB\\\ufffd\ufffd\ufffd\ufffdëO\ufffd}|\ufffd\"\u001d\ufffd\ufffd7#\ufffd\u000b\ufffd\ufffd(\ufffd8t\u001f\ufffd#g\ufffd\u0006E)\ufffd\ufffd\ufffd\ufffd*\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0011\ufffdi@\ufffd\n\u0014\ufffd\ufffdg\ufffd\ufffd6t\ufffd\u0001\ufffd\ufffd\ufffd\ufffd\ufffd9U\u0011\ufffd\ufffd\u0010\ufffd\ufffd\u0000@u\u000501\ufffd\u000f\u0010S\ufffd\ufffd\ufffd1\u001d{\u000bF\ufffd\ufffdL\ufffd\ufffd\ufffd\u0014\ufffd\n=\ufffd\ufffd,\ufffd\ufffd8\ufffd\u000bF\ufffdo\u001f\ufffd\ufffdL\ufffd\ufffd\u0007\ra\u0006N\ufffdBz%4ER\ufffdh\ufffdR2\ufffdJ\n]+x\ufffdDw\u0000\ufffdr\ufffd\ufffd\u0000V\ufffdd\u0014\ufffd\u0011\ufffd!\ufffdP\ufffd-`t\ufffd\ufffd\ufffd4\ufffd\ufffd\ufffd5^`\ufffd\ufffdU\ufffdc1\ufffd8N?\ufffd\ufffd\ufffd?\ufffdC\ufffdLs:\ufffd9G\ufffd\u0010\ufffd\t\ufffd|\ufffd\ufffd\t\ufffd\u0004\ufffd\ufffdK\ufffd.\u0003+\ufffd\ufffdH\u0026\ufffd\ufffd\t\ufffd\ufffd\u000f\ufffd\u001c`4\ufffda\ufffd\ufffd\ufffd\ufffdIR\ufffd\r\ufffd\u0002\ufffdp\ufffd\ufffd\ufffd\ufffddV\ufffd J4\ufffd\u0002\u0011\ufffd\ufffd\u001a{\ufffd[\ufffd\ufffd:\u001d\ufffdgk\ufffd\u001d\ufffd춙\ufffdO\u0011S\u0008\ufffda\u0014RΦ4Ś\ufffd\u001c-2.\ufffd3l+Z\ufffd)\u0002\u001d9#\ufffd\ufffd\n!\ufffd\ufffd\u0014i\ufffd4\u0013s#\ufffdDLU\ufffdr5\ufffdq\ufffds\ufffdd\ufffd\u0002m\ufffdS\ufffd\ufffdH%x\ufffd\u0014\ufffd\ufffd\u001e\ufffd\"\ufffd\ufffdZ\ufffd\ufffd45\ufffd'B\ufffd\ufffd|\ufffd|\ufffdCc\ufffd[w\ufffd\ufffdfo\ufffd7\ufffd\ufffdr\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdU\ufffd\ufffdO7\u0006g\ufffd\ufffdc3\ufffd\ufffd\u003c\ufffd\ufffd'\ufffd\u003c\ufffd\ufffd\ufffd`\ufffd\ufffd\ufffd\u0019Ⲱ\ufffd\u0018a-\ufffdOO.\u0007\ufffd\ufffd\ufffdo\ufffd8\ufffdd\ufffd\ufffd\ufffd$|\ufffd9Y\ufffd\ufffd'\ufffdֳr\ufffd\ufffdΰ\u000fT\ufffd\u0012\\\ufffd%\ufffdi\ufffdW\ufffd\r*\ufffdN\ufffd\ufffd\ufffdL\ufffd\u001c\ufffd\u001b\u0015\ufffd\ufffd\ufffdD:\ufffdv\ufffd\ufffd\u0005\ufffd\u0006\ufffdW\ufffdK\ufffd\ufffd/ \ufffd\ufffdn\ufffd/\ufffd8\ufffd\ufffd\ufffdNtR\u0001k\ufffdۉ\u001ab\ufffd\u000cĪ\ufffdr\ufffd\u001d\ufffd\ufffd\u0004\u00150\u0015p\ufffd\u0012\ufffd\ufffd\ufffd\\B\ufffdB{\rl\ufffdk:\ufffd\ufffdl\ufffd\ufffd\u0007\ufffd\ufffdc\u0004\ufffd\ufffdf-\ufffd\ufffd\ufffdk\ufffd,\ufffd[\ufffd\ufffd\ufffd\ufffd5ήP\ufffd\ufffd\u0018\ufffd\ufffd{\ufffd\u0008\ufffd\u001dH\ufffd\u000e\u000bYA\ufffd\ufffdz\ufffdi\r\ufffd\ufffd\u000c\ufffd\u0005\ufffd\ufffd\u0018\ufffd\u0004\ufffd\ufffd\ufffd0#Z\ufffd\u001ae\u0003.h2\ufffd\ufffd\ufffd\ufffdZվ\ufffd\ufffd\ufffdRj\ufffd\ufffd̴7\ufffd`\ufffdH\ufffd4 \ufffdFA\ufffd\ufffd\ufffdB\u0010\ufffdR\ufffd\ufffd\u0013\u0015ǜ\ufffd`.80L\ufffd\ufffdi\ufffdÑg\ufffd\ufffdh\u001a\ufffdm\u0015\ufffd~!\r'\ufffdZ\ufffd'#`\ufffdU\ufffd\ufffdI\u000b~?7\ufffd\u0000p%\ufffd\u0001鿕g2G/\u000c6\u0007\ufffd\ufffd9\u0008F\"Jn|\ufffdR©\u0005+7l\ufffd5\ufffd\ufffdb\u0007\ufffd\t\ufffd\ufffd쮅\ufffd\u003c\ufffdP\ufffdq\ufffdK9G𡱺\u0016\ufffdQT\u003c\ufffd\ufffd坿K\ufffd\ufffd$\ufffd\u000f\ufffdNs\ufffd/\u0008\u0008\ufffd\ufffd\ufffdG\ufffd\ufffd50=\ufffd\ufffd\ufffd\ufffd\ufffd+N؆q`\ufffd:\ufffd]\ufffd\ufffdw\ufffd\ufffdK.\u0011\ufffd\ufffd!\ufffdm\ufffd\ufffd\ufffd֔A\ufffd\ufffd\ufffdլ\u0017S\ufffd\u0000\ufffdݗ\ufffdFA\ufffd\ufffdZs\ufffdW4W\ufffd+[R\ufffdćD\ufffd\ufffdw\ufffd~\ufffd7!%Xg\u0006Ui\ufffd]\u001a\ufffd\ufffd\ufffd\u000c]\u0026\ufffd\ufffd5\ufffd\u003crd\ufffd6\ufffd\ufffdlV$\ufffd\u0005\ufffd\ufffd\r\u000c\\w\u001dl\ufffd\ufffd\ufffdw\ufffd\u0018\ufffd\ufffd\ufffd\ufffd\u0007\ufffd\u001c+\ufffd-n|{\ufffdg\ufffd\u002625G\u0011\ufffd#̹x\ufffd\ufffd)\ufffdǍc\ufffd\ufffdMN,\u0016Ϸ\u0006\ufffd\ufffdY\ufffd\ufffd\ufffdC\ufffd\ufffd\ufffd qy\ufffd*\u001c\ufffdG\u0015$Y\ufffdp\ufffd9º\ufffd\ufffdy\ufffdIw\ufffd\u00153\ufffd`\ufffd\u001d\u001c\r \ufffd63\ufffd\ufffdX\ufffd\ufffd!\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd洐$\u0012p\ufffd\ufffd\ufffd\ufffd\ufffdN\ufffd;\n\ufffd\rav\ufffd\u000f\ufffd\ufffd\ufffdx\ufffd\u0014a\ufffda\ufffd\ufffd\ufffd\u0019\ufffd\ufffd\ufffd\ufffdn\ufffd\ufffdt8\ufffd\ufffd]\u001c\ufffd\ufffd\ufffdp\ufffdD\u0017Ao\u0018\ufffd\ufffd\ufffd\ufffd\u0017!\ufffd\ufffdQ\ufffdM\ufffd\ufffdnqJc\ufffd\ufffdY\ufffd\ufffd3\ufffd\ufffd\ufffd%@A\ufffd\ufffdJ\u000c\ufffdLHs\ufffd\u003c\ufffd\ufffdۘ\ufffd\ufffd\ufffd\ufffd\ufffdY\ufffdDlg\ufffd[٦\ufffdM\u001a\u001a\ufffd\ufffd\ufffdMD\ufffd\u0026o\ufffd=\u0016]\ufffd\ufffd\ufffd^\ufffd\ufffd\u0014\u0013 \ufffd\ufffd\ufffd\ufffdE\ufffdsz\ufffd\ufffd\ufffd\ufffd?|\u0000\ufffd\ufffd^\u001c\ufffd\ufffd${\ufffd\ufffdV\u000bzD\ufffd\ufffdu\u001f\ufffd/~L3\ufffd\ufffd\u0007\ufffd/ǹ\ufffd?]\ufffd\ufffdw\ufffd/\ufffd:]\ufffd\u001b\u001d\ufffdI\u0017\ufffdZ\ufffd\u001f\ufffd\ufffd${\ufffd\ufffdV\u000bzD\ufffd\ufffdx4\ufffd\ufffd8f\ufffd\ufffd\ufffd\u000fz%ɍ\ufffdT\ufffd\ufffd\ufffd\ufffd\u0007`\ufffd\ufffd\u0017\ufffd\ufffd\ufffd\ufffdg\ufffd\ufffd\ufffd\u00030G\ufffd\ufffd\ufffd\u001c%\ufffd#s\ufffdZ\ufffd\ufffd^R\u001e\ufffd\ufffd\ufffd\ufffd\ufffd\u000fN\u001f\ufffd.\ufffd/\ufffdtQ\ufffd=\ufffdEmw\ufffd\u001b\ufffd\u0003\ufffd\ufffd}\ufffdm\ufffdKƚI\ufffd\ufffdZ\ufffd\u0014'I\ufffd\ufffd\ufffd[\ufffd\ufffd\ufffd\u0001u\ufffd3\ufffd\"s:3\ufffd8\ufffd\ufffdh\ufffd\u001dClA\ufffd\u001e[\ufffd\ufffd\ufffd\ufffd=\ufffd]A1S\ufffd$\u0005s\ufffd#\ufffdMMi˔B\ufffd.\ufffd|\ufffd{ \ufffd\ufffd;\ufffd\u00042\ufffd\u000eK\ufffd\ufffdp\ufffdB\ufffd\ufffd\ufffd\ufffdL0\ufffd#9Cs\ufffd\ufffd\"Dז\ufffdfX`͈zn\ufffdY\ufffd-\ufffd\ufffd\u0002\ufffd@\ufffd.\u0015\ufffd\u000e\ufffdcS+a\ufffd\u00019\ufffd15T\u0014C\ufffd\ufffd\u001c\ufffd9\ufffdS\ufffdگ\ufffd\ufffd\ufffd \ufffd2\ufffd\ufffd\ufffd\n\ufffdM\ufffdߕ6\ufffdl\u0016\u001f\ufffd\u0017\ufffd^.;\ufffdꤢ\ufffd\ufffd\ufffdֶhh5ֻ3}\u001a{\ufffd\ufffd\ufffd\u001fS\u001dI\u0010J\ufffd\ufffdE\u0013\ufffd\u003ePzsu\u0007p\ufffdՙH2\rf\ufffdg\ufffd \ufffd\\m\u0015\ufffdMJi\u000b\ufffd\ufffdDŽcB#\ufffdCS\ufffd\ufffd\ufffd6\ufffd0\ufffd\ufffd\u001e\n\ufffdJx/baH\ufffdW\ufffd\ufffd\u0011\ufffd\ufffd\u003c\ufffd\ufffd\u003c\ufffd\u0007\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw\ufffd\ufffdkF\ufffdO˫\ufffdf\ufffd\ufffd\ufffd\ufffdL\ufffd\ufffd\ufffdp\ufffd\ufffd\ufffd\ufffd|\ufffd5\ufffd\ufffdKM;-\ufffd$fzr\ufffd\ufffd\u000c\ufffdZ\ufffd{_\ufffd\ufffd灐\u0019[\ufffd\r#2K\ufffd\ufffd]K\ufffdW'Ÿ\ufffd\ufffd\u003e\ufffdB¥\ufffd\ufffdj\ufffdR\u0010\ufffd\ufffdM\ufffd\ufffd\ufffdb\ufffd`Uz\n\ufffd(\ufffd3\u0013\ufffd\ufffdV\ufffdG\ufffdV\ufffdFo\ufffd\ufffd\ufffd\u0003\ufffdt\ufffd\u000c\u0013\ufffd:bb^\ufffd\ufffd\ufffd\ufffd\ufffdI\ufffd\ufffdZo\ufffd\n\ufffdbo\ufffd\ufffd\u0014\ufffd\ufffd\u000eǛ8\ufffd\u0000\u0007i\ufffd\ufffdL\ufffd\ufffd\ufffd \u000b[\ufffd\ufffd\n\ufffd\ufffd]o\ufffdZi\ufffd\ufffd\ufffd\u0018hu\ufffd\ufffd\ufffdlئ\ufffdu\u0000\n\ufffd\ufffd\u0003\ufffd\ufffdx[6+u\ufffd\"\ufffd\ufffd\ufffd\ufffd\ufffdL-D\ufffd\ufffdwI\ufffds\ufffdM!\ufffd\ufffd\ufffdT\ufffdي5S@\ufffd\ufffd\ufffd\ufffdW8\ufffdhJ\ufffdͨe\u0015\ufffdf%lV\ufffd\ufffdk[(u\ufffd\u0004w\ufffdz\ufffd\ufffd\ufffdN=غ\ufffdͣC.X\u001ejJo\ufffdҾy\ufffd\u0015m\ufffd\ufffdo\u0000\u0026\ufffd\ufffd\u0003\ufffdd\u0014}\u0004\ufffd+\ufffd\ufffd.u\ufffdi\ufffd\ufffdv\ufffd\ufffdkV[\ufffd\ufffd6\ufffd\ufffdM\ufffd\ufffd\u0013\u0012X\ufffd\ufffdEZ\ufffd~] \ufffd/\ufffd(\rS\ufffd\ufffd\ufffd^\u001e\ufffd\ufffd:d\ufffdc\u0019\ufffd\ufffdkw,\u0003,\ufffd\ufffd/\ufffd\ufffd\ufffd\"lU\ufffdl=\ufffd$\ufffd.\ufffdln\ufffd\ufffd\ufffd`7+\ufffd\ufffdH\ufffd\ufffd\ufffd\u001b8\u0012\ufffd\u001f;\ufffd\u001fq\u0007%\ufffd\u00041\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2092\r\nCache-Control: no-cache\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:34 GMT\r\nSet-Cookie: AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:39+05:30","url":"https://ginandjuice.shop/catalog/cart","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta","Referer":"https://ginandjuice.shop/my-account","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/catalog/cart","scheme":"https"},"raw":"GET /catalog/cart HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta\r\nReferer: https://ginandjuice.shop/my-account\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"1806","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:39 GMT","Set-Cookie":"AWSALB=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/, AWSALBCORS=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ\ufffdS\ufffd6\u0018\ufffdB\ufffdvM{7ǘ@\ufffd%\ufffd\u000e\u0018e\ufffdx[\ufffd֮_8\ufffdVbQ\ufffdrm9\ufffd\ufffd\ufffd{$9\ufffd\t6\u0011`v\ufffd\ufffd\u001cG\ufffd\ufffd\ufffd\u001e=\ufffd\u000f\ufffd\ufffd\ufffd\ufffd\ufffd}v\ufffd\"\u00113\ufffd\ufffdP!\ufffd\u000c#\ufffdC\ufffd\ufffd\ufffd\ufffd\u0026_Q\ufffd\ufffd\ufffd\ufffdd$\ufffdE\u0016\ufffd\ufffdax$\ufffd\ufffd\ufffd\t\ufffd\ufffd\ufffd\u0003\ufffd\u001c\ufffd\ufffdTG\u001f:PF\ufffd\ufffd\ufffd\u0019#yD\ufffdX\u0005\u0026!\u00000\ufffd\u000801\ufffd\u001b \u0026\u0002\ufffd\u0004\ufffdij\ufffd\ufffd\\\ufffd\u003c\u0013\u0016\nx\"H\"\u003c늆\"\ufffdB2\ufffd\u0001\ufffdU\ufffdgT\ufffd$\ufffd\ufffdB\u0011/\ufffdV\r-\u000f2\ufffd\n\ufffdg\ufffdg\ufffd\u0008\ufffd̡\ufffd\u0003\ufffd\u0007$\ufffdx\u001a\u0003x\ufffd2\ufffd\ufffd\ufffd\ufffdW\ufffdC\ufffd!\ufffd\ufffd\u0001#f)\ufffdL\ufffdk\ufffd\\\ufffd)ֽV\u0013:N\u0026\u0005\ufffdمko\ufffd[m\ufffd\ufffd\nF\ufffd}\ufffd\td\ufffdC\ufffd\ufffd\ufffd8Nw\ufffd\ufffd\u0002\ufffd\ufffd\u003eF\u003c\u001d:z\ufffd\ufffd\ufffds#\ufffdሇ3\ufffdLl\ufffd\ufffd5\ufffd\ufffdN\u0011\r=\ufffd.\ufffd\u001aC\ufffdQH (OP\ufffdp\ufffd{\ufffd\ufffd\u0013;$\ufffd:\u0018YZ\ufffd\u0016\ufffd\ufffd\ufffd\ufffd\ufffd\u003c\ufffd9\ufffd?\ufffdB\ufffd\ufffd\ufffddX\u00106Cӂ%\ufffd\u000c\u0002EWd\ufffd\ufffdFF\u0003\ufffdv\ufffd}\ufffd$!!\u001a\ufffd\u000c\t\ufffd\u000b\ufffdL\ufffd\ufffd\ufffd\ufffd(Ow\ufffd\u000b)\ufffdb\ufffd\ufffd\ufffdQ\ufffd/\ufffd7\u00120\ufffdS\\\u001dEj\ufffd\ufffdx\u0012sP*\u0010\u0026ɤ\ufffd%!\ufffd\ufffd_\\\\\ufffd\ufffdCn5̧\ufffd\ufffd\ufffd\u000b\ufffd1\u000c\ufffdG\u0007zt\u003e\u000c\ufffdI}xcaq\ufffdz\u0016\u001d\ufffd\u0017\ufffd\ufffd\ufffd?G\ufffd\ufffd\u0016\ufffd\ufffd\ufffd\ufffd\ufffdK\ufffdx\ufffd-\u000fa\ufffdG/zG\ufffd/\ufffd\ufffdp\ufffd\u0005\ufffd[\ufffd?]\ufffd\ufffd!c\ufffd\ufffd\ufffd\ufffd^V\ufffd2\ufffd%\ufffd \u0007\"H\r\ufffd\ufffdA\ufffd\ufffd\ufffd\u001b\ufffd\u0006\r}\u001b\ufffdV\u00084\u0003\ufffdF\ufffd\u001c\ufffd\ufffdHDD\ufffd\ufffd\u0007Pi`\ufffd\ufffd8K\ufffd\u0002\u0013\ufffd\ufffdݩ\ufffd=\ufffd\ufffd{j\ufffd^\u0003\ufffdTo=U\ufffd\u0014LaZ\ufffd^\u000e\ufffd\ufffdb=\ufffd\u0001\ufffd\u0001.W\ufffd\ufffdf|\ufffd\ufffdN\ufffd\ufffd\u0005\ufffd\ufffd-\u00037P\ufffd`\ufffd\u0015\ufffd\ufffd5\tn٦\ufffdQ\ufffd\ufffdj\ufffd\\:Q:a\ufffd\u0006+\ufffd=GiFc\ufffd\ufffdt\ufffd\ufffdpoA2\ufffdz\ufffd\ufffd\ufffd\ufffd[\ufffd\n!\ufffd)h\ufffdZ\u000eh\u0026\u0006\u003e\ufffdfd\u003c,\u0002\ufffd\ufffd2e\u0001\ufffd1\ufffd\ufffd\ufffdԍ\u0014i{\ufffd\ufffdE\u0016\u001e\ufffdBX\ufffd\ufffd\ufffd#\ufffd\ufffdYG\ufffd\r\ufffd\ufffd=Z\ufffd\ufffd\u0006y\u0012\u003e\ufffd\u0012\ufffd \ufffdE\"l\n{A\ufffd\ufffd\ufffd6!\ufffdMoX\u0014\ufffd\ufffdr\u0012\ufffd\ufffd|:\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd7\ufffd\u0015\ufffd}\"\ufffd\u0000g\u003e\ufffd\u000f\ufffd\ufffdo c-,\ufffd\u0011W\ufffd\ufffd\u0004\u000b\"\u0012|\ufffd\u0003\ufffd\u0005\ufffd(\ufffdz\ufffd\u0012\ufffd\ufffd\ufffdJƛM\ufffd\u0017Co3vIѤ\u0007\ufffd\ufffdv\ufffd'\u0008\u001e\ufffd\ufffdU\ufffdF\ufffd\ufffd=\ufffdu\ufffd\u001f\ufffdP\ufffd\ufffdD\ufffd\ufffd6\u0019s\ufffd\u0007\ufffdA\u0000\ufffd\ufffd6\u0002\u0019\ufffd\u0003A\ufffde\ufffd\ufffd\ufffd)\ufffd\u0004ddu\ufffd\u0008kh\u0010r\ufffdV\ufffd\ufffd6\u000f\ufffd^\ufffd\ufffd\ufffd\u001c\ufffd-\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdތ\ufffd\ufffdg\ufffd\ufffdT\ufffdim\u0000}\ufffd\ufffdzC\ufffd5\ufffd\ufffdD\ufffd\ufffd\ufffd\ufffd|ei\ufffd\ufffd\ufffd\ufffd\u0008\ufffdJGK\ufffd\ufffd,;\ufffdd\ufffd\ufffd\t\ufffd\u0012\ufffd\ufffd\u0004ª\ufffdIG\ufffd\ufffdt[\\\ufffdP\ufffd\ufffd\ufffd\ufffd\\\ufffdq\ufffdf\ufffd\u001e\ufffdm\ufffd\ufffd\ufffd[FS\u0010\ufffd\ufffdx2Q\ufffd\t\ufffdB7n%u\ufffd\ufffdKŻ\ufffd \ufffdo\ufffd\ufffd\ufffd%6#\ufffdeV\ufffd\\K\u0005\ufffdM\ufffdV\ufffdT\ufffdkle:\u0006+\ufffdj\u001aO\ufffd:O-st\ufffd\ufffd\ufffd:\u000b\ufffd\u001bo\ufffd̝\u0014\u0004(\ufffd\u001erAB*E\ufffdO\ufffd\ufffd\t\ufffd-)\ufffd\ufffdӄ@ؠ2VS6\ufffd\ufffd\ufffd\ufffdt\ufffd_\ufffd\ufffdw\ufffd\ufffd\ufffdg\ufffd\ufffd\ufffd\u0003}v\ufffdσ\ufffd\ufffd\ufffd\ufffd\ufffd\u0001=\ufffd\u0005\ufffd\r\u0007p\ufffd\ufffd\u0006k\ufffd\ufffd5S\ufffdf\ufffdo\u0005N\ufffd1\ufffd\ufffd\ufffd\ufffdC\ufffd\ufffd\u0018SV\ufffdB\ufffd\u001bC@\ufffd\u003c\ufffd\u0016\u001c\u003c\ufffd\ufffd\ufffdx蝝~\u003cG\ufffd\u0026\u0005J7\ufffd\ufffd\u000c\ufffd64\ufffdL\ufffdT\"\ufffd^4I\ufffdV\ufffdȷ\ufffdfP_\ufffd\ufffd\ufffd\ufffd\ufffd!I\ufffd\ufffdL%!4Ŭ \ufffd\ufffd\ufffd\ufffd\ufffd5/\ufffdJ\ufffd\ufffdO\ufffd\ufffd\ufffd\ufffdn\ufffd~(O\ufffd\u0008'\u0013ym\u0012Ѽ/9\ufffdϋQLŋ\ufffd;\ufffd\u003c\ufffd\ufffd\ufffd\ufffdK\ufffd\ufffd\u0010֚UER\u0019-\ufffd\ufffd\ufffdQ]\ufffd_\ufffd\u000cl\ufffd\ufffd\u0007\ufffd\ufffd\r,\ufffd30\ufffd\ufffd7;\u0003۴\ufffdW\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd,\ufffd3\ufffdm\ufffd\ufffd\u0019\ufffdk\ufffdw\ufffd\ufffd5\ufffd\ufffd\ufffd\u0008\ufffd\n\ufffd\ufffd\ufffd\ufffd\u0005;p\ufffd3\u0004\u0017,\ufffd\ufffd\ufffd\u0014\\\ufffd\u0005\ufffd\ufffd\ufffd \ufffdE\ufffd\ufffd:v\ufffd\ufffdI\ufffd\ufffd\ufffd\ufffd\ufffd\u000f\ufffd\ufffd)\ufffd\ufffd\ufffd5-\ufffd\u000c\ufffd\ufffdC\ufffdr\ufffdnR\ufffdJČ\ufffd\ufffd(d\u003ee\ufffd\\\ufffd]\u0005G\rmw\ufffd\ufffd\u0008\ufffd\ufffdu\ufffd\ufffd\ufffd!\ufffd\ufffds/\ufffd\ufffd~_\ufffd\u0006\ufffdB\ufffd\ufffd\ufffdA\tv\ufffd\ufffd%\ufffd\ufffdf\ufffd\ufffd\u0026\ufffdv\ufffd%\ufffd\ufffdp[\ufffd\u0001\ufffdjt\ufffd\ufffd\ufffdg\ufffd2\ufffd,\ufffdL2\ufffdh\ufffd\ufffdsP\ufffd7P\ufffd\rL,J\u0017`UZ\ufffd\ufffd\ufffd\ufffd)SҔ\ufffd\u0011\ufffd\u0008U\u0007\r\ufffd\ufffd\u0017)/\ufffdM\ufffd\ufffd\ufffd\t\u000b\u0016g(c\ufffdG\ufffd\ufffd\ufffdj=\ufffdO7\r+\ufffd\u0006Ͷ\ufffdj[\ufffd;\ufffd \ufffd\ufffd\ufffd\ufffdev\ufffd^\ufffd\ufffd\ufffd\ufffd]0\ufffd\ufffd߲\ufffd\ufffd\ufffd\ufffd\ufffdߏ\ufffd9\ufffd\ufffd\ufffd =\ufffdd\\\ufffd-\ufffd\u000b\u000e% \u0011g\ufffd|A\ufffd\u001b\ufffdH3\ufffd\ufffd\u0004\ufffdw\ufffd\u0018\ufffdm\ufffdGiZZ\u0018\ufffd\ufffd\ufffd߅rifnNƦԬ\u0002\ufffd2\ufffd\ufffd\ufffd\ufffdX\ufffd\ufffdO$\ufffd\ufffd\ufffd\ufffd\ufffdɫ\ufffd\ufffd\ufffd\ufffd\ufffdG\ufffd\ufffd?\ufffd\u001b\ufffd\ufffd\ufffdͽ\u003c99\u003e\u0008x\ufffd\ufffd5\ufffd\ufffd*\ufffd\ufffdIQ\"e\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdQwP\ufffd\ufffd\ufffd~Ɯ\u000bp=\ufffd\ufffd-\ufffd\ufffdP\ufffd\u0002Ư\ufffd/\ufffdn\ufffd\ufffdT/\u000e\u000cotr\u001eP̎V\ufffdMX\ufffd\ufffd\ufffd\ufffdǺ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd/\ufffd\ufffd-r\ufffdfx\ufffdY\ufffd\u0005J\ufffdvo\ufffdmK\ufffdTD\ufffd$\ufffd/\ufffd\ufffd\ufffdoD\ufffdg\ufffdp\ufffd?\ufffd4A\ufffd\ufffd\ufffd_\ufffd\u0017\ufffd\ufffd\u0016$\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 1806\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:39 GMT\r\nSet-Cookie: AWSALB=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:41+05:30","url":"https://ginandjuice.shop/my-account","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy; AWSALBCORS=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy","Referer":"https://ginandjuice.shop/catalog/cart","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/my-account","scheme":"https"},"raw":"GET /my-account HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy; AWSALBCORS=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy\r\nReferer: https://ginandjuice.shop/catalog/cart\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"no-cache","Content-Encoding":"gzip","Content-Length":"2094","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:41 GMT","Set-Cookie":"AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/, AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd[\ufffdr۸\u0011\ufffd~O\ufffdc\ufffd8\ufffd\tEQr\ufffdg\"i\u0026\ufffd\ufffd|s\ufffd\ufffd\ufffd\ufffd\ufffdn\ufffd/\u0019\u0008\ufffd$\ufffd \ufffd\u0012\ufffddާ\ufffdF\u001f\ufffd\ufffd\ufffdG\ufffd\ufffdt\u0001P2%\ufffd\"\u0015\ufffd\ufffd\ufffd O\u0026\u0016\ufffd\ufffd\u000f\ufffd\ufffd\ufffd\u000f\u000bq=\ufffd\ufffd\ufffd닟\ufffd\ufffd\ufffd\u0003\ufffd阏\ufffd\u0019\ufffd_\u0008~\u00063\ufffd#\ufffd\ufffd\u003er\u0026\ufffd\ufffd,\ufffd\ufffda\ufffdR%\ufffd\ufffdP\u0015p\u003c6b4\r\ufffdR\ufffd\"X\ufffdD\ufffd\r\u001dh@)\ufffdC\ufffdsNՌR\ufffd\u0004f \u0000P\ufffd\u0000LLw\u0003\ufffdTc$pL\ufffdޜ\ufffdE\"S\ufffd!\"\ufffd\ufffdB\u000f\ufffd\u0005\ufffd\ufffdl\u0018\ufffd9#Է\u000f\ufffdQ\ufffdhꃆ0\u0003\ufffdC!\ufffd\u0012\ufffd\")K4R)\u0019z%\ufffdn\u0015\u003c`\ufffd;\ufffdD\ufffdLb\u0000\ufffd\ufffd*o4\u0008܈\ufffd\u0010~$\ufffd=`t\ufffd\ufffd\ufffd4\ufffd\ufffd\ufffd-\ufffdc\ufffd\ufffdU\ufffdc1\ufffd8N?\ufffd\ufffd\ufffdZ\ufffd\ufffd\ufffd\ufffdt\ufffdS\ufffd0!2\u0013\u001a\ufffd\ufffd\ufffd\t\ufffd\u0002\ufffd\ufffd[\ufffdc\u0006VB73\ufffd\u000c\u0002'\ufffd\ufffd?xp\ufffd\ufffdXF9\u0012S\u001f'I\t6bsĢ\ufffdW\ufffd\ufffd\ufffdY݂(\ufffdL\nD8Vj\ufffd9o\ufffd#\ufffdt\ufffd\ufffd\ufffd\u0001vP\ufffd\ufffdf~~\ufffd1\ufffd\ufffd\u001fF\u0011\ufffdlLS\ufffd)\ufffd\ufffd\u003c\ufffd\u0002\u003eö\ufffd\u0005\u001d#Б3\ufffd\ufffd\ufffd0\u000f\ufffd\n\u001a\ufffd\ufffdL\ufffd\ufffdJ315B/\ufffdX%o\ufffd\u0003\u0019g:wMf)Ц:\ufffd\n\u000cT\ufffdWK1\u003e\ufffd!)b\t\ufffd\u0005[JS\ufffdx\"\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd!\u001a\ufffdp\ufffd-\ufffd뽽\ufffd\ufffd\u0018\ufffd˽}\ufffd\ufffdVQ\ufffd\u003e^\u001b\ufffd%C\ufffdM\ufffd\ufffd\ufffd\ufffd/^\ufffd\ufffd\\k\ufffd\ufffd\ufffd\ufffd\ufffdW\ufffd\ufffd\ufffdZC\ufffd\ufffd\u001c\ufffd\u003c\ufffd\ufffd\ufffdG8\ufffd|\ufffdI\ufffd=ÿ\ufffd'\ufffd%\ufffdd1\u001c\u001e\ufffdZ\ufffd\ufffd)6:\ufffd\u003ePMKp\ufffd[\ufffd\ufffd֫h\ufffdW\ufffd\u001d{\ufffdO\ufffdF987*\ufffd\u0001i\ufffd\ufffd\ufffdڭ\ufffd\u0016\u001c\u001b\ufffd_\ufffd.\ufffd\ufffd\ufffd\ufffd\ufffdx\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0002ָ\ufffd\u00135Ă\u0019\ufffdU\ufffd\ufffd\u0000;.;\n*`*\ufffd\ufffd%8\ufffd˩\ufffdh\ufffd\ufffd\u001a\ufffd\u0000\ufffdt\u003c@ـ\ufffd\u001b\ufffd\ufffdc\u0004\ufffd\ufffdf%\ufffd\ufffd\ufffdk\ufffd,\ufffdZ\ufffd\ufffd\ufffd\ufffd5ήP\ufffd\ufffd\u0018\ufffd\ufffd{\ufffd\u0008\ufffd-HΚ\ufffd\ufffd ^N=δ\u0006Rp\u0006\ufffd\u0002\ufffdL\u000cv\u0002\ufffdHe\ufffd\u0011\ufffdj\ufffd\ufffd\u0006\u0017\ufffd\ufffd\ufffd\ufffdڍ\ufffdj\ufffd\ufffd\ufffd?\ufffdZx,3퍮M\u001ci\ufffd\ufffdO\ufffd\ufffd \ufffd\ufffd\ufffd]\u0008bP\ufffd\ufffdwv\ufffd\ufffd\ufffd\ufffd\u0019\ufffd\u0005\u0007\ufffd\ufffd\ufffd)\ufffd|8\ufffd\u000cs\u0011M\ufffd\ufffd\ufffd\ufffd\ufffd/\ufffd\ufffd\ufffdT\ufffd\ufffdh\u0000\ufffd\ufffd\ufffd|8i\ufffd\ufffd\ufffd\ufffd\u0017\u0000.\ufffd; \ufffd\ufffd\ufffdL\ufffd\ufffd\ufffd\ufffd\ufffd\u000068\ufffd`dFɝOXJ8\ufffd`\ufffd\ufffd\r\ufffd6;X\ufffd@;\ufffd\ufffd,\ufffdm\ufffd\r\ufffd3Tf\\\"\ufffd\ufffdZ]\u000b\ufffd**\ufffd^\ufffd\ufffd\ufffd?\ufffdF\ufffd\ufffd\ufffdͱ\ufffd\u001e\ufffd\u000b\u0002\ufffd\ufffd\ufffd\ufffd\ufffd\rb\rL\ufffd\ufffd|s\ufffd\ufffd\ufffd\t\ufffd2\u000e\ufffdP\ufffd\ufffd+\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdK\ufffd\ufffdk\ufffdg\u001b\"x\ufffd5e\u0010\ufffd\ufffdr5\ufffd\ufffd\ufffd\u003e\ufffd~\ufffde\ufffdAP{\ufffd֜\ufffd\u0015\ufffd\u0015\ufffdʆ\ufffd=\ufffd!\u00112\ufffd\ufffd\ufffd\ufffd\ufffd̵cH\tV\ufffdAU\ufffd\ufffd\ufffdFx+9C\ufffd\t\ufffdq\r1\u000f\u001c\ufffd\ufffd\ufffd.5\ufffd\u0014\ufffdv\ufffd\ufffdu\u0003\u0003\ufffd]\u0007\u001b\ufffd\u0018|\ufffd\ufffdAx\ufffd\ufffd|\ufffdʱ\ufffd\ufffd\ufffdƷc6l\"Ss\u0014\ufffd=\ufffd\r\ufffdO\ufffd\u003en\u001c\ufffd4nsb\ufffdx\ufffd1Є\ufffd\ufffd\ufffdf\u001f\u0002\ufffd\u0004\u0006\ufffd\ufffdk\ufffd\ufffdX\u003c\ufffd \ufffd҄\ufffd\ufffd3\ufffd;\ufffd\ufffd\ufffd\ufffdt[[1\ufffd-\u0006\ufffd\ufffd\ufffd\u003e\ufffd\ufffdfF\ufffd\u0003\ufffd\ufffdr\u0008{\ufffdo9v\ufffd\u003e-$\ufffd\u0004\ufffd\ufffd\ufffda\ufffd\ufffd\ufffdv\u0007\ufffd\ufffd%̖\ufffd\u0011\ufffd\ufffd\u0019o\ufffd\"\ufffd0l\\_\ufffd\ufffdH\ufffd\u000e\ufffd\ufffd\ufffd\u001c\ufffd\ufffd\ufffdu\u0017\ufffd60\u0011\ufffd1\ufffdY\u0010\ufffd\u0006\ufffdn\ufffd\ufffdEH\ufffdx\ufffdpӤ\ufffd[\ufffd\ufffdXg\ufffd\ufffd\ufffd\ufffd\u0019\ufffd\ufffd\ufffd\u0012\ufffd\ufffdds%\ufffd[\u0026\ufffd9n\u001e\ufffd\ufffd\ufffd\ufffdn\ufffd\ufffd\ufffd\ufffd\ufffd|\ufffd\ufffd\ufffd\ufffd\ufffde\ufffdV6ii\ufffd\u0016\u001a\ufffd\u0011\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd}m\ufffd\ufffd^\ufffd\ufffd\u0000\ufffd\u003e\ufffd/\ufffd\ufffd\ufffd\ufffd\ufffdק\ufffd\ufffd\ufffd\u0013\ufffdE\ufffd\ufffd@\u0017%\ufffd\u0003]쵠g\ufffd\ufffd\ufffd\ufffd\\|\ufffd}\ufffd1\ufffd\ufffd\ufffd\ufffd\u001c\ufffd\ufffd\ufffdtq\ufffd뜞u\ufffd81:\u003c\ufffd. \ufffd89\ufffdEI\ufffd@\u0017{-\ufffd\u0019\ufffd\ufffd\ufffd\ufffd㘉\ufffd\ufffd\ufffd\ufffd\ufffdB\ufffd;\u0013\ufffd\ufffd\ufffd\ufffd\ufffd'`\ufffd\ufffd\ufffd\ufffdY\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\t\ufffd#\u003c\u003e0GI\ufffd\ufffd\u001c{-\ufffdp/)\u000f\ufffdC\ufffdKN\ufffd\ufffdO@\u0017\ufffd\ufffd\u0003]\ufffdd\u000ftQ\ufffd]\ufffd\u0006\ufffdat\ufffd\ufffd\ufffdms\ufffdX\u0013)5Xk\ufffd\ufffd$\ufffd\ufffd\ufffdz\ufffd;\\7\ufffd\ufffd{\ufffdmdN'\ufffd\u0015ǎ\u0017\ufffd\ufffdc\ufffd-\ufffd\ufffda\ufffd\ufffd\ufffd7\ufffd\ufffd\ufffd+(fʕ\ufffd`\ufffd|\ufffd\ufffd\ufffd)m\u0019S\ufffd\ufffd\u0005\ufffdOo\u0007D2\ufffdQ2\ufffdL\ufffd\ufffdB\ufffd\u003cZ\ufffd\ufffd\"\"\ufffd8\u0013L\ufffdHN\ufffd\u0014\ufffd\ufffd\ufffdЭ%\ufffd\t\u0016X3\ufffd^\ufffdr\u0016zOx\ufffd\ufffd1\ufffd\ufffd\u000b\ufffd\ufffd\u0003\ufffd\ufffd\ufffdJXm@\u000ezL\r\u0015Ő8\ufffd\u0006h\ufffd\ufffdX\ufffd\ufffd\ufffdzx6ȱLig\ufffd\ufffdb\ufffd\ufffd\u000f\ufffdMG\ufffd\ufffdG\ufffd\ufffd^\ufffdE\ufffdV\u001dU\ufffd\u001eU\ufffd\ufffd\u0016\r-\ufffdz\u000f\ufffdOc\ufffd0\u0015\ufffdc\ufffdg\u0012\ufffd\u0012\ufffdtф\ufffd\u000f\ufffd\ufffd\\=\u00004\ufffd:\u0013I\ufffd\ufffd\u000c\ufffd\ufffd\u0018Đ\ufffd\ufffd\ufffd\ufffdI)m\u0001\ufffd\ufffd\ufffdpL\ufffdL\ufffd\ufffd\u0014\ufffd|\ufffdM8\ufffd`\ufffdM\u0001\\\t\ufffd\ufffdX\u0014Q\ufffd\u0015%jD\ufffd\u0013\u000f\ufffd1\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdn\u003c\u0017\ufffd/\ufffd\ufffd]\ufffdO~\ufffd=\ufffd\ufffd}\ufffdk\ufffd\ufffd\ufffd\ufffd~k:\ufffd\u0006\ufffd5\ufffd\ufffdKM;-\ufffd$fzt\ufffd\ufffd\u000c\ufffdZ\ufffd{W\ufffd\ufffd恈\u0019[\ufffd\r#2K\ufffd\ufffd]\ufffd\u001e\ufffdN\ufffdq\ufffd\ufffdݰB¥\ufffd\ufffdr\ufffdR\u0010\ufffd\ufffd]\ufffd|a\ufffd\ufffd\ufffd*\ufffd\ufffdh\u0014\ufffd\ufffd\ufffd\ufffd\ufffdU\ufffdPs\ufffdl#\u001c\ufffd\ufffd\ufffd8\ufffd \ufffd\u0004\ufffd\ufffd11\ufffdy\ufffdj\ufffd$\ufffd|\ufffd\ufffd[\ufffd~\ufffd7In\n\ufffd|\ufffd㍜a\ufffd\ufffd4pE\u0026\ufffd\ufffdi\ufffd\ufffd-v`\t]\ufffd\ufffd6a\ufffd4tmo\u000c\ufffd\ufffd\ufffd\ufffd`G\ufffdlS\ufffd:\u0000e\ufffd\u0013\u0000\ufffd\ufffdy\u001b6+u\ufffd\"\ufffdv\ufffd\ufffd\ufffdL{\ufffd\u0026\ufffdwI\ufffds\ufffdM!\ufffd\ufffd\ufffdT\ufffdي5S@\ufffd\ufffd\ufffd\ufffd\u000b\ufffdj4\ufffd\ufffdfԲ\ufffdi\ufffd\u00126+\ufffd͵{(\ufffdx\ufffd\ufffd@m`\ufffd\ufffdS\u000f\ufffd.e\ufffdY\ufffd\u000b\ufffd\ufffd\ufffd\ufffd[\ufffd\ufffdo\ufffdmE[+\ufffd\u001b\ufffd\ufffd\ufffd\ufffdL2\ufffd\u003e\u0001C@\u001fu\ufffdi\ufffd\ufffd\ufffd^\ufffd\ufffdXim2S\ufffd\u0002U'$\ufffd\ufffd\ufffd\u001ei\ufffdժ@jW\ufffdQ\u001a\ufffd$\ufffd\u001d\ufffd\ufffdP5\ufffdd\ufffdC\u0019\ufffd\ufffdkw(\u0003,\ufffd\ufffd_\u0010S}Eب\ufffd\ufffdx\ufffdIJ]l\ufffd\ufffd\ufffd\ufffd\u0015\ufffdvV\n\ufffd\ufffd\ufffd\ufffd\ufffd7p$\ufffd?v\ufffd?\ufffd\ufffd;\ufffd\u00041\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2094\r\nCache-Control: no-cache\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:41 GMT\r\nSet-Cookie: AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:46+05:30","url":"https://ginandjuice.shop/logout","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq","Referer":"https://ginandjuice.shop/my-account","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/logout","scheme":"https"},"raw":"GET /logout HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq\r\nReferer: https://ginandjuice.shop/my-account\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"0","Date":"Thu, 07 Sep 2023 15:34:47 GMT","Location":"/","Set-Cookie":"AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/, AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure, session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; Secure; HttpOnly; SameSite=None","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"raw":"HTTP/1.1 302 Found\r\nConnection: close\r\nContent-Encoding: gzip\r\nDate: Thu, 07 Sep 2023 15:34:47 GMT\r\nLocation: /\r\nSet-Cookie: AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure\r\nSet-Cookie: session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; Secure; HttpOnly; SameSite=None\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\nContent-Length: 0\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:47+05:30","url":"https://ginandjuice.shop/","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2","Referer":"https://ginandjuice.shop/my-account","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/","scheme":"https"},"raw":"GET / HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2\r\nReferer: https://ginandjuice.shop/my-account\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2128","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:47 GMT","Set-Cookie":"AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/, AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd\u001a\ufffdr\ufffd6\ufffd\ufffdb˻\ufffd\ufffd\ufffdP\ufffd$\ufffd\ufffdL$u\u0012;\u001f\ufffd\ufffd\ufffd[{\ufffd\ufffd\ufffdd \u0012\ufffd\ufffd\ufffd\u0000K\ufffd\ufffd\ufffdH}\ufffd{\ufffd[\u0000\ufffdLI\ufffdH\ufffd\ufffdLn\u0026\u001e\ufffd-\u0000\ufffd\ufffd\ufffd~/\ufffd\ufffd\ufffdg\ufffdO\ufffd\u003e\\\ufffd\ufffd\ufffdN\ufffd仑\ufffd\u0007\ufffd3\ufffdS\u0012\ufffd\ufffdvș\ufffd\u0004\ufffd\ufffd\ufffd\ufffdAF\ufffd̳\ufffd\ufffd\ufffd\ufffd\ufffd\u0001\ufffdY\u0010*\u0015\ufffd\ufffd\ufffd_\ufffd[;\ufffd\ufffd\t\ufffd(\u001f+]p\ufffd\ufffd\ufffd\ufffd6b\ufffd\u0004\u0012T\ufffdH\u0026\ufffd\ufffd\t$T\u0013\u0010$\ufffdco\ufffd\ufffd2\ufffd\ufffd\ufffd \ufffdBS\ufffd\ufffdޒEz\u003e\ufffd肅Է\ufffdǐ+\ufffd\ufffd\ufffd!\ufffd\ufffd\ufffdXH\ufffdBM\ufffd\u0019K5\ufffd,\u001c{\u0015\ufffd\ufffd\u0015\u000eH\ufffd{H\ufffdr\ufffd\u0026H\ufffdw\ufffd\ufffd\ufffd(p\u0018\ufffdI\ufffd\ufffdL\u000e \ufffd\ufffd\u0014O\ufffd\ufffd\ufffd\u000e\ufffdɂ\ufffdY\ufffd\ufffd:\u0011q\ufffdI\ufffd\ufffd\ufffd?\ufffd\ufffd6Q\ufffdLs:y+\u0013\n\u003e\ufffda\u0002\u001e\ufffd$}\u000e?\ufffd(\u001f\ufffd\ufffd\ufffdt\u00148\u0010\ufffd\ufffd\ufffdV\ufffd\ufffd\ufffd\ufffd\n\u0010\ufffdOҴB0b\u000b`\ufffdثj\ufffd\"Pw\u0014\u001aj\u0026\u0005\ufffd\ufffd(5\ufffd\ufffd\ufffd\ufffd\u0011u\ufffd\ufffd\ufffd\u0016\ufffdEJw\ufffd\ufffd\ufffd՜)\ufffd_\u0002\u0011\ufffdlJ3\ufffd)/`\ufffds\ufffd\ufffdQ\ufffd\ufffd\ufffdS@\u001e9\u000b\ufffd\ufffd\u0015\ufffda\ufffd\ufffd\u0011\ufffdd\u0006\ufffd*\ufffdDl\ufffd\u001e\ufffd\ufffdJ\ufffd\ufffd\u0010\u0019g\ufffdpS\ufffd(8\ufffdz\ufffd\u000c\ufffdTJ\ufffdG1\ufffd\ufffd\ufffd\u0014\ufffdD\ufffdBe\ufffd̘\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdM\u001f\ufffd\ufffd_/\ufffd\ufffd\ufffd\ufffd`c5\ufffd\ufffd\ufffd\ufffdЭ\ufffd\ufffdqVT\ufffdO6\ufffd\ufffdt\ufffd\ufffd\u0019\u003c\ufffdn\ufffd\ufffd\u0001T\ufffd\ufffd\u0018\u000e7\ufffd'\ufffd\ufffd\ufffdRZc ZN\u001f\u001e\ufffd\u000f\ufffdӟ\ufffdy\ufffd\ufffd\ufffd5#ܤ\ufffd\u001b\ufffd\ufffd\ufffdx|\ufffdh\ufffd+\ufffd\ufffd\ufffd\ufffdz\ufffd\ufffdV\ufffd\ufffd\ufffd\ufffd\ufffdܠfnX3w\ufffdM.r\r\u0005\ufffd5\ufffdz\u0000-AϩU\ufffd\ufffdh\ufffd(\ufffd\u001as\t\ufffd\ufffd\u0005\u0001\ufffd\ufffd\ufffd\ufffd\ufffd#g\ufffd/\ufffdFG5d\ufffdy;P\u0013R\u0008C\ufffdz\ufffd\u001c\u0011\u0017Ŏ\ufffd\u001a25\ufffd\ufffd\rm\u003e\ufffd\ufffdD?\ufffd\ufffd\u0006\ufffd\u0001iX\ufffd%e\u001d\ufffdo\ufffd\ufffd\ufffd\u0008Ұ\ufffd\u001a\"\ufffd+\ufffdFX\u0016[\ufffd\ufffdK\ufffd5Ʈ \ufffdXB\ufffdj\u001cw\ufffd$g\ufffd@\u0016\ufffd\ufffd\ufffd\ufffd\ufffdZcPp\u0002\ufffd\u0002\ufffdL\ufffdrB\ufffd\ufffdd\ufffd\ufffdZ5\ne\ufffd\\\ufffde\ufffd\ufffds7\ufffd\ufffd\ufffdĿ_\u0015[d*s\ufffdM\ufffd\u001b?\ufffd2+\ufffd\u0010s\ufffd \ufffdw6!\ufffdA)\ufffd{6\"\u0012\ufffd2\u0017\ufffdgaE*I\ufffd\ufffd\ufffd\ufffd\u001d\ufffd\"\ufffd\ufffd0\ufffd\ufffd\u0016o\ufffd)Zxl\ufffd!\ufffd\n\ufffd\ufffd\ufffd\ufffdؠ\ufffdn\ufffd\ufffd\u0026\ufffd\u001a:]\ufffd[\ufffd\ufffd\u001b\ufffdA\ufffd\ufffd=\ufffd\ufffd9T\u000f~.c`\ufffd3ǖn'\ufffd\ufffd_\ufffd)\ufffd\ufffd|O\ufffd\ufffd\ufffdvwz\ufffda\ufffd!ɶ\ufffd\ufffd\u000c|\ufffdY1\ufffdj2_\ufffd̀\u001d\ufffdנZӽ\ufffd~\ufffd\ufffdv\u003er\ufffd\ufffd}-\ufffd\ufffdS܍l\ufffdf,\ufffdM\ufffd\ufffdN\ufffd\u003eL\ufffd\u0000\ufffd\ufffdkT\u001a\u0005\ufffdy\ufffd!\ufffd\ufffdL\ufffd\ufffd\u0013[P6#c\ufffdb:\ufffd9\u0016\ufffd-Up\ufffd\ufffdz\ufffd\ufffd\ufffd\n\ufffd\ufffd\u0004\ufffd탳\ufffd.\ufffd\ufffd\u0005-5\ufffd\ufffd\ufffdo\u0019y\ufffd\u0010\u0003\ufffdܰ\ufffdŲ-4\\\ufffd\ufffd'f\ufffd*\ufffd\ufffdc\ufffd\ufffd\ufffd\ufffd!\ufffd\ufffd\ufffd0@8Ǻ\ufffd%\ufffd7Tt;̚ndE\ufffd\"\ufffd\ufffd\ufffd}\ufffdVļ\ufffd:\ufffdۙl`\ufffd\ufffdS\ufffd\ufffd4v\u0017\ufffdK\r\ufffd\u0007\u0003\ufffd\u0016\ufffdZM\ufffdI8c\ufffdlp\ufffd\ufffd\ufffd\ufffd?d\ufffd#\ufffd\u0013\u0005\ufffdxr\ufffd\ufffd\u000c\u0014r\ufffd!\ufffd\ufffd\ufffd\n\ufffd\u000cb\u0026\ufffdǮ-\ufffd\ufffdd\ufffdݑ)E\ufffd'\ufffdSS\r+Ԭ\ufffd\u0015Vl\ufffde\ufffd\ufffdom\ufffd=\ufffd%\u001d:F7\ufffd\u000b\ufffds\ufffdu\u0019\ufffd@P\u001a\ufffd\ufffdN\ufffd\ufffdi܍-@\ufffd\ufffd\ufffd\ufffd}\ufffdF\ufffdf\ufffd[~\ufffd\u000e'X)\ufffd\ufffd\ufffd\ufffdNE\u0018\ufffdNL%+?\ufffd\ufffd\ufffd\ufffd\ufffd\u000et,-\ufffd\ufffd\ufffd%\ufffdMW\ufffd\ufffd\ufffdV\u0014\ufffd6PA\ufffd욦\ufffd~\ufffd\u00113g\ufffd\ufffd\"\ufffd\ufffd\ufffd|8\ufffdX\ufffd\ufffd+\ufffd\u000f\ufffd2\ufffd\ufffdR\ufffd\u0018\u0002\ufffd\u0007\ufffd\ufffd\ufffdl3b\ufffd\u0015\ufffd\ufffdpUuXl\ufffdBL\u0018\ufffd\u001c\u001e\ufffd\ufffd\u001c7\ufffd\ufffd\ufffdT\ufffd\ufffd\ufffd\ufffdԈ\ufffdө\ufffdĺ\u0015\u0002\u001d,`pO\u0016\ufffd\ufffd\u001f\ufffdr\u0017\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd'wU\ufffd\ufffdOz\ufffd~\ufffd\ufffdV\ufffd\ufffd\ufffd\u0014~|\ufffd\ufffd_g9\ufffd\ufffd\u001e\ufffd\u0019'\ufffd\ufffd\ufffd\ufffd\ufffd٠\ufffd\ufffd\ufffdש斲ʁ\ufffdON\ufffdFr_U\ufffd\ufffdj9\ufffd\ufffd\ufffd\u0018R\ufffd.Ir\u000b\ufffdOe;\u000e\ufffd\ufffd\u0002\u0005\u0006\ufffdG\ufffd\ufffdyE\ufffd\ufffd\ufffdaU\ufffd\ufffdw\ufffd\ufffd^{\u00176\ufffd\u000f\u0026/\ufffd-aY\u0001g\ufffd@\ufffd\u001e\ufffdr\ufffda\u003cXC\ufffd*A\ufffdx\ufffd8\ufffdj3\ufffd\u000eV\ufffd\ufffd$,ȗ\u0015\ufffdI'\ufffd\u000e\u000f\u0010\ufffd\u0015\u0016\ufffd\ufffd2I9\ufffdB\ufffd^Dz\ufffd\u0005E\ufffd\ufffd\t\ufffd\ufffd\u001eh\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdn\ufffd\ufffd\ufffd\ufffd\ufffd{\u0026%\ufffd\u001c\ufffd2Ò\ufffd\ufffd\ufffdݪH\u001dBS\ufffd\ufffdK\ufffdә\ufffd\u0011\ufffdso\ufffd\ufffd\u0013\ufffdFh_\ufffd@\u000bxG1}@{a\"\u001c|P\ufffd\ufffd\ufffd\ufffdS\nB.\ufffd\ufffd\t\ufffd\ufffd?Kl\ufffdLͱ4\u001dߒE\u0014B\ufffd$\ufffd\ufffdn\ufffd\ufffd\ufffd\ufffd\ufffd\u0008\ufffdm7#\u00023L\ufffd\u001e\ufffd\ufffd)z\u0013\ufffd\\\ufffd\u0005\ufffdD\ufffd\ufffd\ufffd9B\ufffd\ufffd\ufffd\u000f\ufffd\r\ufffd\ufffd\ufffdy\u000c\ufffdDQ\u001c\ufffd\ufffds2\ufffd\ufffd\ufffd7pl('2\ufffd-\ufffdF\ufffd\ufffd\ufffdh\ufffd\u0015q}\ufffdw\ufffd\ufffd\\\ufffd\ufffd\ufffdG\ufffdZY\ufffd7\ufffd\u0015\ufffdw+\ufffd,\ufffd ´\ufffd'Tϥi\ufffdM\u0014pS\ufffd\ufffd@\ufffd\ufffd\ufffd%\ufffd\ufffd*L\ufffd\ufffdF1\ufffd\ufffd\ufffd\u000c{i\ufffdHJ\u0013̋\ufffd%\ufffd}D\ufffd\u000e\ufffd\\\ufffd\ufffd4\ufffd\ufffd\ufffd\u0014\ufffd\"\u003cm[\ufffdSKޛ\ufffd(\ufffd\ufffd+ߚC\ufffd\ufffd\u003c0\ufffd\u001e\u000e\ufffd͟\ufffdIt\u001a\ufffd.^\u003cS\ufffd\ufffd)5\ufffd\ufffd\ufffd\ufffd\u001f\ufffd_/\ufffd^\ufffd\ufffd\ufffdж_\u0019P\ufffd\ufffd@\ufffd\ufffd(\ufffd\ufffd\ufffd\ufffd\ufffdJ2\ufffd\ufffd-ߥ\ufffd4\ufffd\ufffd\n\u000be\ufffdJộ\u0003\ufffdc\ufffd\ufffd\ufffd\ufffd-'\u000c\ufffdT\ufffd_\ufffdS\ufffd\ufffd\ufffd\ufffdS9}ji\ufffdY\ufffd\u001e\ufffd7\n\ufffd\ufffdxz۩\ufffd\ufffd\ufffd\ufffdLv.#\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdOg`\"A\ufffd\ufffdX\ufffd\ufffd\ufffd\ufffd;\ufffd\ufffd\ufffd\ufffd\ufffdmio\ufffd\ufffda\ufffd\ufffd\ufffd0\ufffdپ\ufffd\ufffdM\ufffd`0\u0006i\ufffd\u0015\ufffd\ufffd\ufffd\u0017\ufffd\ufffd\ufffd*i\ufffd\ufffdK\ufffdk%\ufffd\ufffdƥ]\ufffd\ufffd\ufffd\ufffd\ufffd34\ufffd!\ufffd\ufffd\ufffd!QF#\u001f\ufffd\ufffd'oKf\ufffd%(\ufffdn_\ufffd\ufffd\ufffd\ufffd\u0000\ufffdt\ufffd\"M\ufffd\ufffd\ufffdc\n\ufffd\ufffd\ufffd\ufffd\ufffdީ\ufffd˷\ufffd|[\ufffd\ufffdd\u001a\ufffd\u0014\ufffd\u0019\ufffdQ\ufffd\ufffdY\u0008\ufffd\ufffd/\ufffd썵\u00070ա\u00020^ѭ\ufffd]e=T]\ufffd\ufffd\ufffd!\ufffd\ufffd\ufffd\u000e\ufffdZڋz\ufffd@\ufffdI\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdK\ufffd!\\ \ufffdKG\u0001\ufffdu\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u001c_\ufffd\ufffd\ufffdZ\ufffd\ufffdΩ-H\ufffd\ufffd\ufffd\u0003ʒw\ufffd\ufffdΎ퉒!j\ufffd\ufffd\ufffd\u0011\ufffdMx\ufffd^\ufffd\ufffd\u003cw\ufffd^\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdշ\u0008[\u000ff[Æ\ufffd\ufffd\ufffd\ufffd\ufffdM\ufffd\ufffd\u0001w\ufffdRLF2*\u0026\ufffdaJ\ufffd\ufffdZ\ufffd\u001f\u000blT\ufffd(\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2128\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:47 GMT\r\nSet-Cookie: AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
+{"timestamp":"2023-09-07T21:04:52+05:30","url":"https://ginandjuice.shop/catalog","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom","Referer":"https://ginandjuice.shop/","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/catalog","scheme":"https"},"raw":"GET /catalog HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom\r\nReferer: https://ginandjuice.shop/\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2876","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:52 GMT","Set-Cookie":"AWSALB=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/, AWSALBCORS=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd\\}r\ufffd6\u0016\ufffd\ufffd\ufffd@\ufffd\ufffd\ufffd=#Q\ufffd\ufffd\ufffdĒ:\ufffd\ufffd\ufffdi\ufffd8\ufffd\ufffd\ufffdvwv\u003c\u0010\tI\ufffdA\ufffd\u0005@)\ufffdLf\ufffd\u001a{\ufffd\ufffd\ufffd\u001eeO\ufffd\u000f\u0000EQ\u0012)\ufffd.;^\ufffd֓\ufffdE\u0000\ufffd\ufffd\ufffd\ufffd\ufffd\u0017(\ufffd\ufffdg\ufffdn.\ufffd\ufffd\ufffd\ufffd\u0012\ufffdU\ufffd\ufffd_t\ufffd/\u0004?\ufffd1\ufffd\ufffd\ufffdh\u001e\u0019\r\ufffd\ufffdX\ufffda\ufffd)\ufffd\ufffd\ufffd\ufffd\ufffdl2\u003c\ufffdÈhzR6\ufffd\ufffd\ufffd\ufffd\ufffdצ\ufffd\ufffd\u0006$\u0008\ufffdI5cD\ufffd\tQۘi\u0016\ufffdP^z\u003c\u0008\u00084\ufffd\n\u001e\ufffd J\ufffd\ufffdA@\u0014F!\u000eHϙP2\ufffd\ufffdP\u000e\ufffdx\ufffdH\ufffdzΔ\ufffdj\ufffd\ufffdɄz\ufffda\u001e\ufffd(\ufffdD4`\ufffd0\u0003#\ufffd\ufffd;\u0019n\ufffd\u00134RH\n\ufffd\ufffdd\u0004\ufffd \ufffd\u0001{\ufffd\u0005N\ufffd\ufffd(\u0000\ufffd\ufffd\u0007\ufffd\ufffd\ufffdMKQ\ufffdE\ufffd\ufffd\ufffd\u0003بY\u0004+S\ufffd\ufffdj~\ufffd\u0013l[\ufffd\u003c\ufffd8\u001c\ufffd\u000c\ufffd\ufffdV\ufffd\ufffdqR\ufffdUQ\ufffdH\ufffd\ufffd\ufffd~\ufffd)\ufffd\u001a芆\ufffd9\u000e\ufffd3\ufffdM\u000c:B\ufffdc\u001eu\ufffdv\ufffd\ufffd\ufffd\ufffd\ufffd\u0003\ufffd\ufffdP8j\ufffd(\ufffd0\ufffd\ufffd\u0004Q\ufffd\ufffdd-'\ufffdT\ufffd\u001c\ufffd)\ufffdC\ufffd1,eϱ\ufffd\ufffd\ufffd\ufffd\ufffd\u0010zV\u0008\u000cQ\ufffdަޏ\ufffdD\ufffd\u000f#\ufffd0: \u0002+\ufffdfh\u0012\ufffd\u0010\u003e\u0003\ufffdhJ\u0006\u0008dd\ufffd\ufffdfV\ufffd\ufffd\ufffdB\ufffd\ufffd!\u0017H\u0011\ufffdh8҃\ufffd\ufffd\u0003\u0019\ufffd\ufffd\t)\ufffdjf\ufffd\ufffdR\ufffdM\ufffd\ufffd\u0002te\ufffdӥh\u000bv\u0010\u000f\u0003\u000e\ufffd\u0005\ufffd\u0012\ufffd\ufffd.\ufffd\ufffdԽ\ufffd\ufffd\ufffdB=\ufffdJ\ufffd\ufffdd\ufffd\ufffd\ufffd\ufffd\u001b@w\ufffd\ufffdc{\ufffd\ufffd\ufffd\u001af\ufffd\u000f\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd!\ufffd\ufffdN\ufffd\ufffd9\ufffdε\ufffd\ufffdY~\u003c\ufffdG\ufffd'\ufffd\ufffd!\ufffd\ufffd`\ufffdv\ufffd\ufffdkkpuļ\ufffd5\ufffd\ufffd\u0018\ufffdW\ufffdy\ufffd^\ufffd\ufffd\ufffd\ufffd\ufffd\u0008\ufffd2\u0003\u000eD\ufffd\u000c\ufffd\ufffd\u0019Zkk\ufffd\ufffdur\ufffd\u000e\ufffd\ufffd\ufffdX\ufffd\u0019\ufffd6Jp@\ufffd#5\u0026\u0006\ufffdg`֠\ufffd\u001csi\ufffd\ufffd\u000b\u000c\ufffdV\ufffd\ufffd\u0016k\ufffdl\ufffd\ufffdD\ufffd\u001c\ufffdڼ\ufffdP\u001dV0\ufffda\ufffdv\ufffd\ufffd6\ufffd՚9lr\ufffdI\u0013\ufffd\u001a\ufffd\ufffd8\ufffd*\ufffd\u0017\ufffdm₎\u0005+㰍-\ufffd\u0019\ufffd\u0010\u0017L\ufffd\ufffd\ufffd\ufffdܴa,\u001d\u0019\ufffdh$\u000e\ufffd\ufffd]\ufffdH\ufffd\u0000\ufffd\ufffd}\ufffdq\ufffd5\ufffd\ufffdn\u001fd\u0006\ufffd\ufffdԃX)\ufffdH[\ufffd\ufffd\ufffd\ufffdX\ufffd:M0Q\u000c\ns\ufffd\u0018V\ufffd\ufffd%\ufffd\ufffd2\u0012\u003cZ\ufffdT\ufffd\ufffd\u0011\ufffd\u001c\ufffd\ufffd\ufffd\u0012\u000b\u000fx\ufffd\ufffd\ufffd\ufffdv(\ufffdŬ\"\ufffd\ufffd͘\ufffdj[\u0002g\ufffd\ufffd\ufffd\u001b[\u0013\ufffd\u003c\u001e\ufffd\ufffdA\ufffd\ufffdV\ufffdY#i\ufffdT)'\ufffd~\u0017\u0002ڼs\ufffdZ\ufffd\ufffdG\ufffd\u0017\ufffd\ufffdǻ0\ufffd\u000fK\u003c\ufffd\ufffdXg\\ͧ\ufffdz\u0013\ufffd\ufffd\u001b\ufffd\ufffde\ufffd/\ufffd\u001c\ufffd\u000b\ufffd\ufffd#D\ufffd\ufffd\u0012\u001b\ufffd\ufffd\ufffd\ufffd\ufffd\u0015\ufffd\ufffd\u0019J\u003e\ufffdF\ufffdo\ufffd\ufffd\ufffd\ufffd\u001ea\ufffd\u001e\u0016+֚\u0004\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdS`+I\ufffd%\ufffdW\ufffd\u001a\ufffd]p\ufffdݖ^r\ufffd\ufffd\u001b\ufffd\ufffdF\ufffd\ufffdlx\ufffdU\ufffd\ufffdH\ufffd\ufffdv5\ufffdb\n\u001f@\ufffd_\u001b\ufffd\ufffd\ufffd\ufffd\u0004X\ufffd\ufffds\ufffds\n\ufffd\ufffdQ\u00265CŢ\ufffd%d\ufffd\u0013\ufffdR\u0013\u0007\ufffd\ufffd\ufffd\u000c\ufffdW\u000e/ҽ\ufffd\ufffd\ufffd:\ufffd\u0011\u0001\ufffd\u0005q\ufffdkcm\ufffdu\ufffd\ufffd0)\ufffd\ufffd0\\Dش\ufffdEl[\ufffd\u0004\u000c\u000f\u0005e\ufffd\ufffd\u0002%\ufffd\ufffd\u001bo\ufffd\u0006\ufffd\ufffd\u0000\ufffdZ\ufffdd2=\ufffd\u001dޘ\ufffd\ufffd\ufffd\ufffd\ufffd[\ufffd\ufffda\u0014';\u0026\ufffda\ufffd{\u00123\ufffd9\u0016\u000e\ufffd\u0018\ufffdȘ3_\ufffd෦\u001d*\u0019\ufffd\u0008'\ufffdB\ufffd\ufffd\ufffd\ufffd\u0008\ufffd$\ufffd\ufffd\ufffd֪\ufffdg\ufffd!\ufffd\u0026\\A\ufffd\u001e\ufffd\ufffdζ\u0012\ufffd܋\ufffd\ufffdpD\ufffd%#\ufffd\ufffd\ufffd썿WK\ufffdS\ufffdw'\ufffd\ufffd\u0004\u0018.\ufffdof\ufffd\ufffd\u001d\ufffd\u001d\ufffdT^F\ufffd2\u001e\u0004T%\ufffdَ\ufffd\ufffd\\\ufffd\ufffd\u003cn\ufffd4\ufffdg\ufffdu\ufffdV\ufffdi\ufffd\ufffdp4\ufffdvp\ufffd\ufffd-\ufffde7\ufffd\ufffd\ufffd\ufffdkS\ufffdЅ\ufffd\u003ea\ufffd\u001f\ufffd\u0010\ufffd\ufffdm\ufffd\ufffd}\ufffdmڮb\ufffdͻpK\u0008\ufffdI\ufffd\ufffd\ufffd\t\ufffd\ufffd\ufffd\ufffd)\ufffd@nE\u0012\ufffd\ufffd2\u0003\ufffd\ufffdɈ\u000bJ$\ufffd\ufffd\ufffd|Ř\ufffdra\ufffdu\ufffd+\u000f\ufffd\ufffd4\u00032\u001d_\u0026d\ufffd^\ufffdߌ\ufffd\u0001\u0004G\ufffd\ufffd)$\ufffd\u000e\ufffd;\ufffd\ufffd\ufffd\ufffd\u000f\ufffd=u犆\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd7\u0005\ufffd\ufffd\ufffd\ufffdsv\ufffd\ufffd\n\ufffd\"\u0019\u0003\ufffd\u000ecƲC\u0004\ufffd\ufffd\ufffd^\ufffd|\ufffd\ufffd\ufffd\u0007h\ufffd+t\ufffd\u0015\ufffd\ufffd\ufffd\ufffd\ufffd}\ufffd\ufffd\ufffd\ufffdm2;\ufffd\ufffd\ufffd\u001e^O\ufffd\ufffdG\ufffd\u003ez\ufffdl\ufffd\ufffd\ufffd4\u0011\u0000\ufffdz\ufffdt\u0008z\ufffdh38\ufffdNqM\ufffd\ufffd\ufffdf\ufffd\u0001\ufffd\\\u0010P#\ufffd\ufffd@y\ufffd\rp\ufffd7o\ufffd\ufffd\u00115SJ\ufffdβ\ufffd3\ufffd\\\ufffdv\ufffd\ufffd\ufffd\ufffd\u0011:\ufffdgG\ufffd\ufffdF\ufffd\ufffdjW\u0015\ufffd\ufffd\ufffd9\u001a\ufffd\ufffd,\ufffds\ufffd\ufffd\ufffddtS\ufffdn֘\ufffdM\ufffdj\ufffd\ufffd\ufffd\ufffdL\ufffd\u0013D\ufffd\"\ufffd;\ufffd^\r\ufffd\ufffd\ufffd\u0013\ufffd'\ufffd\ufffd\ufffd\ufffd\u0017\ufffd\ufffd\ufffd\ufffd\u003e\u001a\u0012\ufffd\u003e/Cj'\ufffd\ufffd\u0007\ufffd\ufffd\ufffd %\u0002GmP\ufffd%@\u0012\u0016ڲ\\AB\u0008\ufffd{)\ufffdf\ufffd9\u0016\ufffd\rO\ufffd\ufffd\u0018\ufffd0R5\u0014edc\u003c«5]\ufffd\ufffd\ufffdL~\ufffd\ufffd{\ufffdm\ufffd\ufffd\u0006\ufffd\ufffd%\ufffd\ufffd\ufffd\ufffd\ufffd]\ufffd\ncٌ@\u003c\ufffdB\ufffd\ufffd\u0011\ufffdj\ufffd\ufffd(\u001cm\ufffdc\ufffd鿛ӡKK\ufffd.\ufffdw\u000f\ufffde\ufffd\ufffd;\ufffde\\\ufffdG\u0005֯\ufffd:e\ufffdȾk\ufffd\ufffd\ufffd/\ufffd?v\u000eܣ\ufffd\ufffd\ufffd@\ufffd\ufffd\ufffd~\ufffd\ufffd#%S\ufffd\u0013\ufffd\n\ufffd\ufffd\ufffdƚ\ufffd\u000c\ufffd튑l\ufffdJBwa\ufffd(\ufffdY绛i\ufffd\ufffd\u001d=\u0016\ufffd\ufffdC\ufffd\ufffd\ufffdn\u0000ש\u001a\ufffd\ufffd\ufffd\ufffd\ufffd\u00161\ufffd\\7\u0013\"\u0018\ufffd\ufffd'\ufffd\ufffd\u0017m\ufffd\ufffd\ufffdn\ufffduX1\\\ufffdL\u001c\ufffd\ufffd_\ufffd\\~T\u0002\n\u0012.\ufffdֻZ\ufffd\ufffd\ufffdx\ufffdQ\ufffdp\ufffd\ufffdu.\ufffd\u0003\u001a\ufffd\ufffd\u001f\ufffdL\ufffd!\ufffd\ufffdj \ufffd8Z\ufffd\ufffd\ufffd\ufffd\u000b\ufffd\ufffd\ufffd\u001b\ufffd\u001dW\ufffd\ufffd\u0000+o|\ufffd\ufffdtNJBx\ufffd\t\ufffd\u0026\ufffd4\ufffd\u001d\ufffd\u001f\u0014#'\u0015cW*\ufffd\ufffd)햁\ufffd\u0004ڻ\ufffd\u0001\ufffdۊ\ufffd\ufffd\ufffdR\ufffd\ufffd-\ufffd\ufffd#1\ufffd\ufffd\ufffd\ufffd\ufffd\u0001)\ufffd\ufffd\ufffdq\ufffd\ufffd\u0019:\u0007\ufffd\ufffd?mQr춏v\u0003\ufffd\u0017\u0015#V6D\ufffdx\ufffd\"\ufffdݎ\ufffd=\ufffd\ufffdHi?\u0016\u003e\ufffd\ufffd\ufffdڻ\u0001_\ufffd\ufffdb\ufffdJ)\r\ufffd\ufffde\ufffd\ufffd\ufffd9\ufffdi\u0000\u0011\ufffd[\ufffd\ufffd\ufffd\ufffdN\ufffdܓ\ufffd\u001d\ufffd\ufffd\ufffd7'\ufffd\ufffd\ufffd]\ufffdR\ufffd\ufffdJp\ufffd\ufffd\ufffd\ufffdx\ufffdI}\ufffd\ufffd\ufffd\u001e\ufffd\ufffd\u0008pU\ufffd(y\ufffd\ufffd\ufffd\ufffd\"\u0016\ufffdN\ufffdՄ\ufffdG\ufffd\u0004\ufffd\u0007\ufffd\ufffd\ufffd\ufffd#\ufffd\ufffd_\ufffd\u001c\ufffd\ufffd\ufffdF\ufffdpd\u0003\ufffdk\u001a\u0012YIt|\ufffd\ufffd\u001d\ufffdLJ;\ufffdX\ufffdoI\ufffd\ufffdR\ufffd\ufffdIE\u0019+\t\ufffd\u0004\ufffdj\ufffd\ufffd@\ufffd\ufffdgP\ufffd\ufffd\ufffd\ufffdс{\ufffd#{\ufffdVկN\ufffd\ufffd#\u001abv\ufffd\ufffdٝ\ufffd\ufffd\ufffd\ufffd^h\ufffd\ufffd+1C\ufffd\ufffd_\ufffd߷\ufffd\ufffd\ufffd2PV\ufffd.%\ufffd\ufffd\ufffd\ufffdg\ufffdU\ufffd\ufffdw\u000c\u0001\ufffd\u001a\ufffd'w\ufffd\ufffd\ufffd\u000e\ufffd`\ufffd/R\u0018\ufffdꎆw\ufffdnLfD\ufffd\ufffd\ufffd\u0001\u0015\ufffd$\ufffd\ufffdך\ufffd\ufffd!ܥ(Z\ufffd˔R\ufffd\n\ufffd1E\u000b\ufffd\u0013P\ufffd\ufffd\ufffdpE\ufffd\n\ufffd\ufffd\ufffdO\ufffd\ufffd;\u003euO\ufffd\u0018\ufffd\r_\ufffd\ufffd;\ufffdg\ufffd3'䆜\ufffd3\tS\ufffd\ufffd(\ufffd8\ufffdʷ\ufffd\ufffd\ufffd\ufffd8\ufffd:gF\ufffd\ufffd \ufffd\ufffds\ufffd\ufffd4\ufffd\ufffd\u001f\ufffd\ufffdl\ufffd\ufffd\ufffdK\u0026\ufffd燝\ufffd\ufffd\ufffd\u0002\t\ufffd\ufffdd\u003c\ufffd\u0007\u0016\u0006\u0004\ufffd|\n\ufffd\ufffd\ufffd\ufffd\"\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdS.\ufffd?\ufffd\u003eA\ufffd\u000cb\u001cR5C|\ufffdF:f\ufffd\u003e\ufffd`\ufffd\ufffd\u001a\ufffd\u0010\ufffdǓus;\ufffd|\ufffdX,\ufffd\u0004f\"S\ufffd\ufffdeİ\ufffdza\ufffd\ufffdqУ/d\u0011,\t\u003cy\ufffd1\u003c\ufffd*\ufffdg\ufffd9\ufffd\ufffd\ufffdk\u00172\ufffdտ\ufffd'U[\ufffdɔ.\ufffd-\ufffd\ufffd\u001fd-\ufffd\"S\ufffd\ufffd\ufffdy\ufffd9\ufffd\ufffdP\ufffd\u0008\u001c䃿6\ufffd\ufffd\ufffdN\u0004q3iZ=n\ufffd\ufffd|k|0\ufffd\r\u0005\ufffd%\ufffd\u0002\ufffd\ufffd\ufffd\ufffd\u0018\t\ufffd\ufffd\ufffd\u001c%\ufffd\u001f\ufffd\ufffd\u001c^\ufffd\u0026\ufffd\ufffd\ufffd\ufffdm\u0007\ufffds\ufffd;c\ufffd\ufffd$\ufffd\u001fV\ufffd\ufffd\u0018:\ufffd\u001c\u0003\ufffd9\ufffdG\ufffdO\ufffd㛟n\ufffd\ufffd\ufffd{\ufffd\ufffdtz|=\ufffd.\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdk\ufffdӟ\ufffd͗\u001c\ufffd[9z\ufffd9\u0005ؿ\ufffdk\ufffd\ufffdٿB\ufffdH\ufffdҺ6\ufffdy\u003c\ufffdxذ-ۄ\\:\u0001k\ufffd\ufffd\ufffd{\ufffd\n=H\ufffd\ufffd1_'\u000f=F\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd~eD\ufffd\u0003o\u000cվ\ufffd\ufffdm\ufffd\ufffd \u0026\r\ufffd\ufffd\ufffd\ufffd\ufffdz\ufffd\ufffd\ufffd\u000f\ufffd\u0004~:D:\u0012\ufffd\ufffd\u0018b{\ufffd)\ufffdR\ufffdG\ufffd\ufffdG)r+B'\ufffd\u0026\ufffd\ufffd\ufffdt\r\ufffd\ufffd\ufffd[\ufffd@\u000cR\u0010+\ufffd\ufffdߞ\ufffd\ufffd\ufffd\u0026\u0008\ufffdY'lS\u0010R\ufffd\ufffdk\u001d\u0018h\ufffd\ufffd?\u0002\ufffd%\ufffdd\ufffd\u000e\ufffdR\ufffd7 \ufffd\ufffd;+:\ufffdt\ufffd\ufffd\ufffd6\u0005\ufffd\ufffd\ufffd\u001e04\ufffd\u0015El\ufffd\ufffd\ufffd\ufffdh\ufffd\ufffd\u0017\ufffd\ufffdIU}\u001f3҅\ufffd\u0005\u0016\n\r\u0008D3b\ufffd\ufffdn3#\ufffd\ufffd\u0017u7\ufffd\ufffd\u0007\u0008\ufffd\ufffds\ufffd\ufffdQ\ufffdD╬\u0007\ufffd\ufffd^q\ufffd\ufffd\u0004\ufffd\ufffd\ufffd\u001e\ufffd\ufffd\ufffd\ufffd\u0001\ufffd\ufffd\\)\ufffdk\u0006\ufffd\ufffd\u000b\ufffd\u000f\ufffd\u001d\ufffd\u000e8\ufffdZ\u000e\ufffdZ\ufffdn\ufffd\ufffd=\ufffdb@\ufffdj\ufffd\ufffd\u0016\ufffd\u0026(\ufffdsr\u000b\u0012X\ufffd\ufffd\u0001e\ufffd\ufffd\ufffd\ufffdզ*#C\u0026\ufffd\u0007\ufffd^o\ufffd{\ufffdMy\ufffd\ufffd*\ufffd^\ufffd\ufffdo\u0015\ufffd\ufffd\u003e\ufffd\ufffdN\ufffd^a\ufffd\ufffd\ufffd\ufffdcAuj\ufffd\ufffd\u0014\ufffd\ufffdo\u0013\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd?\ufffd\u0001\ufffd\ufffd\ufffd\u0019\ufffd\ufffd\u0002\ufffdYQ\ufffd\ufffdA\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2876\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:52 GMT\r\nSet-Cookie: AWSALB=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}}
diff --git a/pkg/input/formats/testdata/ginandjuice.proxify.yaml b/pkg/input/formats/testdata/ginandjuice.proxify.yaml
new file mode 100644
index 0000000000..4ebbb308d2
--- /dev/null
+++ b/pkg/input/formats/testdata/ginandjuice.proxify.yaml
@@ -0,0 +1,269 @@
+timestamp: 2024-02-20T19:24:13+05:30
+url: https://ginandjuice.shop/blog/post?postId=3&source=proxify
+request:
+ header:
+ Accept-Encoding: gzip
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: ginandjuice.shop
+ method: GET
+ path: /blog/post
+ scheme: https
+ raw: |+
+ GET /blog/post?postId=3&source=proxify HTTP/1.1
+ Host: ginandjuice.shop
+ Accept-Encoding: gzip
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+
+response:
+ header:
+ Content-Encoding: gzip
+ Content-Type: text/html; charset=utf-8
+ Date: Tue, 20 Feb 2024 13:54:13 GMT
+ Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None
+ X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62
+ X-Frame-Options: SAMEORIGIN
+ raw: |+
+ HTTP/1.1 200 OK
+ Connection: close
+ Content-Encoding: gzip
+ Content-Type: text/html; charset=utf-8
+ Date: Tue, 20 Feb 2024 13:54:13 GMT
+ Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/
+ Set-Cookie: AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure
+ Set-Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None
+ X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62
+ X-Frame-Options: SAMEORIGIN
+
+---
+timestamp: 2024-02-20T19:24:13+05:32
+url: https://ginandjuice.shop/users/3
+request:
+ header:
+ Accept-Encoding: gzip
+ Authorization: Bearer 3x4mpl3t0k3n
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+ host: ginandjuice.shop
+ method: POST
+ path: /catalog/product
+ scheme: https
+ raw: |+
+ POST /catalog/product?productId=3 HTTP/1.1
+ Host: ginandjuice.shop
+ Authorization: Bearer 3x4mpl3t0k3n
+ Accept-Encoding: gzip
+ Connection: close
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
+
+response:
+ header:
+ Content-Encoding: gzip
+ Content-Type: text/html; charset=utf-8
+ Date: Tue, 20 Feb 2024 13:54:13 GMT
+ Set-Cookie: AWSALB=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=fFcCUjmQguQy820Y8xrnRypp3KBWSPk6; Secure; HttpOnly; SameSite=None
+ X-Backend: 2235790d-f089-4324-8ac0-f64cc96f2460
+ X-Frame-Options: SAMEORIGIN
+ body: |
+
+
+
+
+
+
+
+
+
+ Fruit Overlays - Product - Gin & Juice Shop
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Fruit Overlays
+
+
+
+ $92.79
+
+
+
+
+ Description:
+ When it comes to hospitality presentation is key, and nothing looks better than a well-dressed drink. We know gin fans like plenty of fruit in their glasses, some a bit more than they really need, but hey ho, each to their own. But what about fruit not inside your glass, but classily arranged over your glass? In comes Fruitus overlayus, the best way to jazz up any party. The possible colour combinations are endless, just picture that! All you need is a nice selection of small fruits, or maybe even use our Fruit Curliwurlier to add a dash of even more drama, and we will do the rest. This one is a real winner at our Christmas and New year’s outings, give it a go and turn some heads.
+ CONTENTS: 12 cocktail sticks.
+ HOW TO USE: Let your creative juices flow (Pun intended), and spend some time working on your colour coordination, try not to think too much about it, just do it! Pick up one of the Fruitus overlayus sticks and carefully slide the fruit along until there is a small space on either end of the stick. Balance the stick across the rim of the glass. Ta-Da! Your first fruit overlay. Keep going until you have as many overlays as you need. You can always purchase more at any time with a discount on bulk buys.
+
+
+
+
+
+
+
+
+
+
+
+ View cart
+
+
+
+
+
+
+
+
+
+
+ raw: |+
+ HTTP/1.1 200 OK
+ Connection: close
+ Content-Encoding: gzip
+ Content-Type: text/html; charset=utf-8
+ Date: Tue, 20 Feb 2024 13:54:13 GMT
+ Set-Cookie: AWSALB=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/
+ Set-Cookie: AWSALBCORS=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure
+ Set-Cookie: session=fFcCUjmQguQy820Y8xrnRypp3KBWSPk6; Secure; HttpOnly; SameSite=None
+ X-Backend: 2235790d-f089-4324-8ac0-f64cc96f2460
+ X-Frame-Options: SAMEORIGIN
\ No newline at end of file
diff --git a/pkg/input/formats/testdata/openapi.yaml b/pkg/input/formats/testdata/openapi.yaml
new file mode 100644
index 0000000000..93a37df7a3
--- /dev/null
+++ b/pkg/input/formats/testdata/openapi.yaml
@@ -0,0 +1,582 @@
+openapi: 3.1.0
+info:
+ title: VAmPI
+ description: OpenAPI v3 specs for VAmPI
+ version: '0.1'
+servers:
+ - url: http://hackthebox:5000
+components: {}
+paths:
+ /createdb:
+ get:
+ tags:
+ - db-init
+ summary: Creates and populates the database with dummy data
+ description: Creates and populates the database with dummy data
+ operationId: api_views.main.populate_db
+ responses:
+ '200':
+ description: Creates and populates the database with dummy data
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example: 'Database populated.'
+ /:
+ get:
+ tags:
+ - home
+ summary: VAmPI home
+ description: >-
+ VAmPI is a vulnerable on purpose API. It was created in order to
+ evaluate the efficiency of third party tools in identifying
+ vulnerabilities in APIs but it can also be used in learning/teaching
+ purposes.
+ operationId: api_views.main.basic
+ responses:
+ '200':
+ description: Home - Help
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example: 'VAmPI the Vulnerable API'
+ help:
+ type: string
+ example: 'VAmPI is a vulnerable on purpose API. It was created in order to evaluate the efficiency of third party tools in identifying vulnerabilities in APIs but it can also be used in learning/teaching purposes.'
+ vulnerable:
+ type: number
+ example: 1
+ /users/v1:
+ get:
+ tags:
+ - users
+ summary: Retrieves all users
+ description: Displays all users with basic information
+ operationId: api_views.users.get_all_users
+ responses:
+ '200':
+ description: See basic info about all users
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ email:
+ type: string
+ example: 'mail1@mail.com'
+ username:
+ type: string
+ example: 'name1'
+ /users/v1/_debug:
+ get:
+ tags:
+ - users
+ summary: Retrieves all details for all users
+ description: Displays all details for all users
+ operationId: api_views.users.debug
+ responses:
+ '200':
+ description: See all details of the users
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ admin:
+ type: boolean
+ example: false
+ email:
+ type: string
+ example: 'mail1@mail.com'
+ password:
+ type: string
+ example: 'pass1'
+ username:
+ type: string
+ example: 'name1'
+ /users/v1/register:
+ post:
+ tags:
+ - users
+ summary: Register new user
+ description: Register new user
+ operationId: api_views.users.register_user
+ requestBody:
+ description: Username of the user
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ username:
+ type: string
+ example: 'John.Doe'
+ password:
+ type: string
+ example: 'password123'
+ email:
+ type: string
+ example: 'user@tempmail.com'
+ required: true
+ responses:
+ '200':
+ description: Sucessfully created user
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example: 'Successfully registered. Login to receive an auth token.'
+ status:
+ type: string
+ enum: ['success', 'fail']
+ example: 'success'
+ '400':
+ description: Invalid request
+ content: {}
+ /users/v1/login:
+ post:
+ tags:
+ - users
+ summary: Login to VAmPI
+ description: Login to VAmPI
+ operationId: api_views.users.login_user
+ requestBody:
+ description: Username of the user
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ username:
+ type: string
+ example: 'John.Doe'
+ password:
+ type: string
+ example: 'password123'
+ required: true
+ responses:
+ '200':
+ description: Sucessfully logged in user
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ auth_token:
+ type: string
+ example: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzAxNjA2MTcsImlhdCI6MTY3MDE2MDU1Nywic3ViIjoiSm9obi5Eb2UifQ.n17N4AxTbL4_z65-NR46meoytauPDjImUxrLiUMSTQw'
+ message:
+ type: string
+ example: 'Successfully logged in.'
+ status:
+ type: string
+ enum: ['success', 'fail']
+ example: 'success'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: ['fail']
+ example: 'fail'
+ message:
+ type: string
+ example: 'Password is not correct for the given username.'
+ /users/v1/{username}:
+ get:
+ tags:
+ - users
+ summary: Retrieves user by username
+ description: Displays user by username
+ operationId: api_views.users.get_by_username
+ parameters:
+ - name: username
+ in: path
+ description: retrieve username data
+ required: true
+ schema:
+ type: string
+ example: 'John.Doe'
+ responses:
+ '200':
+ description: Successfully display user info
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ username:
+ type: string
+ example: 'John.Doe'
+ email:
+ type: string
+ example: 'user@tempmail.com'
+ '404':
+ description: User not found
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: ['fail']
+ example: 'fail'
+ message:
+ type: string
+ example: 'User not found'
+
+ delete:
+ tags:
+ - users
+ summary: Deletes user by username (Only Admins)
+ description: Deletes user by username (Only Admins)
+ operationId: api_views.users.delete_user
+ parameters:
+ - name: username
+ in: path
+ description: Delete username
+ required: true
+ schema:
+ type: string
+ example: 'name1'
+ responses:
+ '200':
+ description: Sucessfully deleted user
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example: 'User deleted.'
+ status:
+ type: string
+ enum: ['success', 'fail']
+ example: 'success'
+ '401':
+ description: User not authorized
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ example: 'fail'
+ enum: ['fail']
+ message:
+ type: string
+ example: 'Only Admins may delete users!'
+ '404':
+ description: User not found
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ example: 'fail'
+ enum: ['fail']
+ message:
+ type: string
+ example: 'User not found!'
+ /users/v1/{username}/email:
+ put:
+ tags:
+ - users
+ summary: Update users email
+ description: Update a single users email
+ operationId: api_views.users.update_email
+ parameters:
+ - name: username
+ in: path
+ description: username to update email
+ required: true
+ schema:
+ type: string
+ example: 'name1'
+ requestBody:
+ description: field to update
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ email:
+ type: string
+ example: 'mail3@mail.com'
+ required: true
+ responses:
+ '204':
+ description: Sucessfully updated user email
+ content: {}
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: ['fail']
+ example: 'fail'
+ message:
+ type: string
+ example: 'Please Provide a valid email address.'
+ '401':
+ description: User not authorized
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: ['fail']
+ example: 'fail'
+ message:
+ type: string
+ example: 'Invalid Token'
+ /users/v1/{username}/password:
+ put:
+ tags:
+ - users
+ summary: Update users password
+ description: Update users password
+ operationId: api_views.users.update_password
+ parameters:
+ - name: username
+ in: path
+ description: username to update password
+ required: true
+ schema:
+ type: string
+ example: 'name1'
+ requestBody:
+ description: field to update
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ password:
+ type: string
+ example: 'pass4'
+ required: true
+ responses:
+ '204':
+ description: Sucessfully updated users password
+ content: {}
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: ['fail']
+ example: 'fail'
+ message:
+ type: string
+ example: 'Malformed Data'
+ '401':
+ description: User not authorized
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: ['fail']
+ example: 'fail'
+ message:
+ type: string
+ example: 'Invalid Token'
+ /books/v1:
+ get:
+ tags:
+ - books
+ summary: Retrieves all books
+ description: Retrieves all books
+ operationId: api_views.books.get_all_books
+ responses:
+ '200':
+ description: See all books
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ Books:
+ type: array
+ items:
+ type: object
+ properties:
+ book_title:
+ type: string
+ user:
+ type: string
+ example:
+ Books:
+ - book_title: 'bookTitle77'
+ user: 'name1'
+ - book_title: 'bookTitle85'
+ user: 'name2'
+ - book_title: 'bookTitle47'
+ user: 'admin'
+ post:
+ tags:
+ - books
+ summary: Add new book
+ description: Add new book
+ operationId: api_views.books.add_new_book
+ requestBody:
+ description: >-
+ Add new book with title and secret content only available to the user
+ who added it.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ book_title:
+ type: string
+ example: 'book99'
+ secret:
+ type: string
+ example: 'pass1secret'
+ required: true
+ responses:
+ '200':
+ description: Sucessfully added a book
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example: 'Book has been added.'
+ status:
+ type: string
+ enum: ['success', 'fail']
+ example: 'success'
+ '400':
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: ['fail']
+ example: 'fail'
+ message:
+ type: string
+ example: 'Book Already exists!'
+ '401':
+ description: User not authorized
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: ['fail']
+ example: 'fail'
+ message:
+ type: string
+ example: 'Invalid Token'
+ /books/v1/{book_title}:
+ get:
+ tags:
+ - books
+ summary: Retrieves book by title along with secret
+ description: >-
+ Retrieves book by title along with secret. Only the owner may retrieve
+ it
+ operationId: api_views.books.get_by_title
+ parameters:
+ - name: book_title
+ in: path
+ description: retrieve book data
+ required: true
+ schema:
+ type: string
+ example: 'bookTitle77'
+ responses:
+ '200':
+ description: Successfully retrieve book info
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ book_title:
+ type: string
+ example: 'bookTitle77'
+ owner:
+ type: string
+ example: 'name1'
+ secret:
+ type: string
+ example: 'secret for bookTitle77'
+ '401':
+ description: User not authorized
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: ['fail']
+ example: 'fail'
+ message:
+ type: string
+ example: 'Invalid Token'
+ '404':
+ description: Book not found
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ enum: ['fail']
+ example: 'fail'
+ message:
+ type: string
+ example: 'Book not found!'
\ No newline at end of file
diff --git a/pkg/input/formats/testdata/postman.json b/pkg/input/formats/testdata/postman.json
new file mode 100644
index 0000000000..50044e4ee9
--- /dev/null
+++ b/pkg/input/formats/testdata/postman.json
@@ -0,0 +1,159 @@
+{
+ "info": {
+ "_postman_id": "20a3fd41-6a86-4e49-8860-f796559d0223",
+ "name": "advancedsearch",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "List Projects, Assets and Hosts",
+ "protocolProfileBehavior": {
+ "disableBodyPruning": true
+ },
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Content-Type",
+ "name": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://127.0.0.1:8000/api/v1/search/",
+ "protocol": "http",
+ "host": ["127", "0", "0", "1"],
+ "port": "8000",
+ "path": ["api", "v1", "search", ""]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "List Assets and Hosts",
+ "protocolProfileBehavior": {
+ "disableBodyPruning": true
+ },
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Content-Type",
+ "name": "Content-Type",
+ "type": "text",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://127.0.0.1:8000/api/v1/search/?projectId=1,2",
+ "protocol": "http",
+ "host": ["127", "0", "0", "1"],
+ "port": "8000",
+ "path": ["api", "v1", "search", ""],
+ "query": [
+ {
+ "key": "projectId",
+ "value": "1,2"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "List Hosts",
+ "protocolProfileBehavior": {
+ "disableBodyPruning": true
+ },
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "Content-Type",
+ "name": "Content-Type",
+ "type": "text",
+ "value": "application/json"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://127.0.0.1:8000/api/v1/search/?projectId=1,2&assetId=1,2",
+ "protocol": "http",
+ "host": ["127", "0", "0", "1"],
+ "port": "8000",
+ "path": ["api", "v1", "search", ""],
+ "query": [
+ {
+ "key": "projectId",
+ "value": "1,2"
+ },
+ {
+ "key": "assetId",
+ "value": "1,2"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Search Request",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "name": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n\t\"query\": \"query\",\n\t\"projectId\": [4,3,4],\n\t\"assetId\": [2,3,4],\n\t\"hostId\": [1,2,3],\n \"limit\": 10,\n \"offset\": 10\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://127.0.0.1:8000/api/v1/search/",
+ "protocol": "http",
+ "host": ["127", "0", "0", "1"],
+ "port": "8000",
+ "path": ["api", "v1", "search", ""]
+ }
+ },
+ "response": []
+ }
+ ],
+ "protocolProfileBehavior": {}
+}
diff --git a/pkg/input/formats/testdata/swagger.yaml b/pkg/input/formats/testdata/swagger.yaml
new file mode 100644
index 0000000000..cd20b1849f
--- /dev/null
+++ b/pkg/input/formats/testdata/swagger.yaml
@@ -0,0 +1,37 @@
+swagger: "2.0"
+info:
+ title: Sample API
+ description: API description in Markdown.
+ version: 1.0.0
+host: localhost
+basePath: /v1
+schemes:
+ - https
+paths:
+ /users:
+ get:
+ summary: Returns a list of users.
+ description: Optional extended description in Markdown.
+ produces:
+ - application/json
+ responses:
+ 200:
+ description: OK
+ /users/{userId}:
+ get:
+ summary: Returns a user by ID.
+ parameters:
+ - in: path
+ name: userId
+ required: true
+ type: integer
+ default: 1
+ description: Parameter description in Markdown.
+ - in: query
+ name: test
+ type: string
+ enum: [asc, desc]
+ description: Type of query
+ responses:
+ 200:
+ description: OK
\ No newline at end of file
diff --git a/pkg/input/formats/yaml/multidoc.go b/pkg/input/formats/yaml/multidoc.go
new file mode 100644
index 0000000000..dc258408c1
--- /dev/null
+++ b/pkg/input/formats/yaml/multidoc.go
@@ -0,0 +1,78 @@
+package yaml
+
+import (
+ "io"
+ "os"
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ YamlUtil "gopkg.in/yaml.v3"
+)
+
+// YamlMultiDocFormat is a Yaml format parser for nuclei
+// input HTTP requests with multiple documents separated by ---
+type YamlMultiDocFormat struct {
+ opts formats.InputFormatOptions
+}
+
+// New creates a new JSON format parser
+func New() *YamlMultiDocFormat {
+ return &YamlMultiDocFormat{}
+}
+
+var _ formats.Format = &YamlMultiDocFormat{}
+
+// proxifyRequest is a request for proxify
+type proxifyRequest struct {
+ URL string `json:"url"`
+ Request struct {
+ Header map[string]string `json:"header"`
+ Body string `json:"body"`
+ Raw string `json:"raw"`
+ } `json:"request"`
+}
+
+// Name returns the name of the format
+func (j *YamlMultiDocFormat) Name() string {
+ return "yaml"
+}
+
+func (j *YamlMultiDocFormat) SetOptions(options formats.InputFormatOptions) {
+ j.opts = options
+}
+
+// Parse parses the input and calls the provided callback
+// function for each RawRequest it discovers.
+func (j *YamlMultiDocFormat) Parse(input string, resultsCb formats.ParseReqRespCallback) error {
+ file, err := os.Open(input)
+ if err != nil {
+ return errors.Wrap(err, "could not open json file")
+ }
+ defer file.Close()
+
+ decoder := YamlUtil.NewDecoder(file)
+ for {
+ var request proxifyRequest
+ err := decoder.Decode(&request)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return errors.Wrap(err, "could not decode json file")
+ }
+ if strings.TrimSpace(request.Request.Raw) == "" {
+ continue
+ }
+
+ rawRequest, err := types.ParseRawRequestWithURL(request.Request.Raw, request.URL)
+ if err != nil {
+ gologger.Warning().Msgf("multidoc-yaml: Could not parse raw request %s: %s\n", request.URL, err)
+ continue
+ }
+ resultsCb(rawRequest)
+ }
+ return nil
+}
diff --git a/pkg/input/formats/yaml/multidoc_test.go b/pkg/input/formats/yaml/multidoc_test.go
new file mode 100644
index 0000000000..6275eae593
--- /dev/null
+++ b/pkg/input/formats/yaml/multidoc_test.go
@@ -0,0 +1,28 @@
+package yaml
+
+import (
+ "testing"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/stretchr/testify/require"
+)
+
+func TestYamlFormatterParse(t *testing.T) {
+ format := New()
+
+ proxifyInputFile := "../testdata/ginandjuice.proxify.yaml"
+
+ expectedUrls := []string{
+ "https://ginandjuice.shop/blog/post?postId=3&source=proxify",
+ "https://ginandjuice.shop/users/3",
+ }
+
+ var urls []string
+ err := format.Parse(proxifyInputFile, func(request *types.RequestResponse) bool {
+ urls = append(urls, request.URL.String())
+ return false
+ })
+ require.Nilf(t, err, "error parsing yaml file: %v", err)
+ require.Len(t, urls, len(expectedUrls), "invalid number of urls")
+ require.ElementsMatch(t, urls, expectedUrls, "invalid urls")
+}
diff --git a/pkg/input/provider/chunked.go b/pkg/input/provider/chunked.go
new file mode 100644
index 0000000000..904acd5b02
--- /dev/null
+++ b/pkg/input/provider/chunked.go
@@ -0,0 +1,29 @@
+package provider
+
+import (
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
+)
+
+// TODO: Implement ChunkedInputProvider
+// 1. Lazy loading of input targets
+// 2. Load and execute in chunks that fit in memory
+// 3. Eliminate use of HybridMap since it performs worst due to marshal/unmarshal overhead
+
+// ChunkedInputProvider is an input providing chunked targets instead of loading all at once
+type ChunkedInputProvider interface {
+ // Count returns total targets for input provider
+ Count() int64
+ // Iterate over all inputs in order
+ Iterate(callback func(value *contextargs.MetaInput) bool)
+ // Set adds item to input provider
+ Set(value string)
+ // SetWithProbe adds item to input provider with http probing
+ SetWithProbe(value string, probe types.InputLivenessProbe) error
+ // SetWithExclusions adds item to input provider if it doesn't match any of the exclusions
+ SetWithExclusions(value string) error
+ // InputType returns the type of input provider
+ InputType() string
+ // Switches to the next chunk/batch of input
+ NextChunk() bool
+}
diff --git a/pkg/input/provider/http/multiformat.go b/pkg/input/provider/http/multiformat.go
new file mode 100644
index 0000000000..ee95079ef4
--- /dev/null
+++ b/pkg/input/provider/http/multiformat.go
@@ -0,0 +1,120 @@
+package http
+
+import (
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats/burp"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats/json"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats/openapi"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats/swagger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats/yaml"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
+)
+
+// HttpMultiFormatOptions contains options for the http input provider
+type HttpMultiFormatOptions struct {
+ // Options for the http input provider
+ Options formats.InputFormatOptions
+ // InputFile is the file containing the input
+ InputFile string
+ // InputMode is the mode of input
+ InputMode string
+}
+
+// HttpInputProvider implements an input provider for nuclei that loads
+// inputs from multiple formats like burp, openapi, postman,proxify, etc.
+type HttpInputProvider struct {
+ format formats.Format
+ inputFile string
+ count int64
+}
+
+// NewHttpInputProvider creates a new input provider for nuclei from a file
+func NewHttpInputProvider(opts *HttpMultiFormatOptions) (*HttpInputProvider, error) {
+ var format formats.Format
+ for _, provider := range providersList {
+ if provider.Name() == opts.InputMode {
+ format = provider
+ }
+ }
+ if format == nil {
+ return nil, errors.Errorf("invalid input mode %s", opts.InputMode)
+ }
+ format.SetOptions(opts.Options)
+ // Do a first pass over the input to identify any errors
+ // and get the count of the input file as well
+ count := int64(0)
+ parseErr := format.Parse(opts.InputFile, func(request *types.RequestResponse) bool {
+ count++
+ return false
+ })
+ if parseErr != nil {
+ return nil, errors.Wrap(parseErr, "could not parse input file")
+ }
+ return &HttpInputProvider{format: format, inputFile: opts.InputFile, count: count}, nil
+}
+
+// Count returns the number of items for input provider
+func (i *HttpInputProvider) Count() int64 {
+ return i.count
+}
+
+// Iterate over all inputs in order
+func (i *HttpInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {
+ err := i.format.Parse(i.inputFile, func(request *types.RequestResponse) bool {
+ return callback(&contextargs.MetaInput{
+ ReqResp: request,
+ })
+ })
+ if err != nil {
+ gologger.Warning().Msgf("Could not parse input file while iterating: %s\n", err)
+ }
+}
+
+// Set adds item to input provider
+// No-op for this provider
+func (i *HttpInputProvider) Set(value string) {}
+
+// SetWithProbe adds item to input provider with http probing
+// No-op for this provider
+func (i *HttpInputProvider) SetWithProbe(value string, probe types.InputLivenessProbe) error {
+ return nil
+}
+
+// SetWithExclusions adds item to input provider if it doesn't match any of the exclusions
+// No-op for this provider
+func (i *HttpInputProvider) SetWithExclusions(value string) error {
+ return nil
+}
+
+// InputType returns the type of input provider
+func (i *HttpInputProvider) InputType() string {
+ return "MultiFormatInputProvider"
+}
+
+// Close closes the input provider and cleans up any resources
+// No-op for this provider
+func (i *HttpInputProvider) Close() {}
+
+// Supported Providers
+var providersList = []formats.Format{
+ burp.New(),
+ json.New(),
+ yaml.New(),
+ openapi.New(),
+ swagger.New(),
+}
+
+// SupportedFormats returns the list of supported formats in comma-separated
+// manner
+func SupportedFormats() string {
+ var formats []string
+ for _, provider := range providersList {
+ formats = append(formats, provider.Name())
+ }
+ return strings.Join(formats, ", ")
+}
diff --git a/pkg/input/provider/interface.go b/pkg/input/provider/interface.go
new file mode 100644
index 0000000000..cb48fc7676
--- /dev/null
+++ b/pkg/input/provider/interface.go
@@ -0,0 +1,126 @@
+package provider
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider/http"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider/list"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
+ configTypes "github.com/projectdiscovery/nuclei/v3/pkg/types"
+ errorutil "github.com/projectdiscovery/utils/errors"
+)
+
+var (
+ ErrNotImplemented = errorutil.NewWithFmt("provider %s does not implement %s")
+ ErrInactiveInput = fmt.Errorf("input is inactive")
+)
+
+const (
+ MultiFormatInputProvider = "MultiFormatInputProvider"
+ ListInputProvider = "ListInputProvider"
+ SimpleListInputProvider = "SimpleInputProvider"
+)
+
+// IsErrNotImplemented checks if an error is a not implemented error
+func IsErrNotImplemented(err error) bool {
+ if err == nil {
+ return false
+ }
+ if strings.Contains(err.Error(), "provider") && strings.Contains(err.Error(), "does not implement") {
+ return true
+ }
+ return false
+}
+
+// Validate all Implementations
+var (
+ // SimpleInputProvider is more like a No-Op and returns given list of urls as input
+ _ InputProvider = &SimpleInputProvider{}
+ // HttpInputProvider provides support for formats that contain complete request/response
+ // like burp, openapi, postman,proxify, etc.
+ _ InputProvider = &http.HttpInputProvider{}
+ // ListInputProvider provides support for simple list of urls or files etc
+ _ InputProvider = &list.ListInputProvider{}
+)
+
+// InputProvider is unified input provider interface that provides
+// processed inputs to nuclei by parsing and providing different
+// formats such as list,openapi,postman,proxify,burp etc.
+type InputProvider interface {
+ // Count returns total targets for input provider
+ Count() int64
+ // Iterate over all inputs in order
+ Iterate(callback func(value *contextargs.MetaInput) bool)
+ // Set adds item to input provider
+ Set(value string)
+ // SetWithProbe adds item to input provider with http probing
+ SetWithProbe(value string, probe types.InputLivenessProbe) error
+ // SetWithExclusions adds item to input provider if it doesn't match any of the exclusions
+ SetWithExclusions(value string) error
+ // InputType returns the type of input provider
+ InputType() string
+ // Close the input provider and cleanup any resources
+ Close()
+}
+
+// InputOptions contains options for input provider
+type InputOptions struct {
+ // Options for global config
+ Options *configTypes.Options
+ // NotFoundCallback is the callback to call when input is not found
+ // only supported in list input provider
+ NotFoundCallback func(template string) bool
+}
+
+// NewInputProvider creates a new input provider based on the options
+// and returns it
+func NewInputProvider(opts InputOptions) (InputProvider, error) {
+ // optionally load generated vars values if available
+ val, err := formats.ReadOpenAPIVarDumpFile()
+ if err != nil && !errors.Is(err, formats.ErrNoVarsDumpFile) {
+ // log error and continue
+ gologger.Error().Msgf("Could not read vars dump file: %s\n", err)
+ }
+ extraVars := make(map[string]interface{})
+ if val != nil {
+ for _, v := range val.Var {
+ v = strings.TrimSpace(v)
+ // split into key value
+ parts := strings.SplitN(v, "=", 2)
+ if len(parts) == 2 {
+ extraVars[parts[0]] = parts[1]
+ }
+ }
+ }
+
+ // check if input provider is supported
+ if strings.EqualFold(opts.Options.InputFileMode, "list") {
+ // create a new list input provider
+ return list.New(&list.Options{
+ Options: opts.Options,
+ NotFoundCallback: opts.NotFoundCallback,
+ })
+ } else {
+ // use HttpInputProvider
+ return http.NewHttpInputProvider(&http.HttpMultiFormatOptions{
+ InputFile: opts.Options.TargetsFilePath,
+ InputMode: opts.Options.InputFileMode,
+ Options: formats.InputFormatOptions{
+ Variables: generators.MergeMaps(extraVars, opts.Options.Vars.AsMap()),
+ SkipFormatValidation: opts.Options.SkipFormatValidation,
+ RequiredOnly: opts.Options.FormatUseRequiredOnly,
+ },
+ })
+ }
+}
+
+// SupportedFormats returns all supported input formats of nuclei
+func SupportedInputFormats() string {
+ return "list, " + http.SupportedFormats()
+}
diff --git a/pkg/core/inputs/hybrid/hmap.go b/pkg/input/provider/list/hmap.go
similarity index 87%
rename from pkg/core/inputs/hybrid/hmap.go
rename to pkg/input/provider/list/hmap.go
index bbc675dc6b..f7e3bd14c6 100644
--- a/pkg/core/inputs/hybrid/hmap.go
+++ b/pkg/input/provider/list/hmap.go
@@ -1,6 +1,6 @@
-// Package hybrid implements a hybrid hmap/filekv backed input provider
+// package list implements a hybrid hmap/filekv backed input provider
// for nuclei that can either stream or store results using different kv stores.
-package hybrid
+package list
import (
"bufio"
@@ -19,6 +19,7 @@ import (
"github.com/projectdiscovery/hmap/filekv"
"github.com/projectdiscovery/hmap/store/hybrid"
"github.com/projectdiscovery/mapcidr/asn"
+ providerTypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover"
@@ -34,8 +35,9 @@ import (
const DefaultMaxDedupeItemsCount = 10000
-// Input is a hmap/filekv backed nuclei Input provider
-type Input struct {
+// ListInputProvider is a hmap/filekv backed nuclei ListInputProvider provider
+// it supports list type of input ex: urls,file,stdin,uncover,etc. (i.e just url not complete request/response)
+type ListInputProvider struct {
ipOptions *ipOptions
inputCount int64
excludedCount int64
@@ -59,7 +61,7 @@ type Options struct {
// New creates a new hmap backed nuclei Input Provider
// and initializes it based on the passed options Model.
-func New(opts *Options) (*Input, error) {
+func New(opts *Options) (*ListInputProvider, error) {
options := opts.Options
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
@@ -67,7 +69,7 @@ func New(opts *Options) (*Input, error) {
return nil, errors.Wrap(err, "could not create temporary input file")
}
- input := &Input{
+ input := &ListInputProvider{
hostMap: hm,
ipOptions: &ipOptions{
ScanAllIPs: options.ScanAllIPs,
@@ -105,109 +107,39 @@ func New(opts *Options) (*Input, error) {
return input, nil
}
-// Close closes the input provider
-func (i *Input) Close() {
- i.hostMap.Close()
- if i.hostMapStream != nil {
- i.hostMapStream.Close()
- }
+// Count returns the input count
+func (i *ListInputProvider) Count() int64 {
+ return i.inputCount
}
-// initializeInputSources initializes the input sources for hmap input
-func (i *Input) initializeInputSources(opts *Options) error {
- options := opts.Options
-
- // Handle targets flags
- for _, target := range options.Targets {
- switch {
- case iputil.IsCIDR(target):
- ips := expand.CIDR(target)
- i.addTargets(ips)
- case asn.IsASN(target):
- ips := expand.ASN(target)
- i.addTargets(ips)
- default:
- i.Set(target)
- }
- }
-
- // Handle stdin
- if options.Stdin {
- i.scanInputFromReader(readerutil.TimeoutReader{Reader: os.Stdin, Timeout: time.Duration(options.InputReadTimeout)})
- }
-
- // Handle target file
- if options.TargetsFilePath != "" {
- input, inputErr := os.Open(options.TargetsFilePath)
- if inputErr != nil {
- // Handle cloud based input here.
- if opts.NotFoundCallback == nil || !opts.NotFoundCallback(options.TargetsFilePath) {
- return errors.Wrap(inputErr, "could not open targets file")
+// Iterate over all inputs in order
+func (i *ListInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {
+ if i.hostMapStream != nil {
+ i.hostMapStreamOnce.Do(func() {
+ if err := i.hostMapStream.Process(); err != nil {
+ gologger.Warning().Msgf("error in stream mode processing: %s\n", err)
}
- }
- if input != nil {
- i.scanInputFromReader(input)
- input.Close()
- }
+ })
}
- if options.Uncover && options.UncoverQuery != nil {
- gologger.Info().Msgf("Running uncover query against: %s", strings.Join(options.UncoverEngine, ","))
- uncoverOpts := &uncoverlib.Options{
- Agents: options.UncoverEngine,
- Queries: options.UncoverQuery,
- Limit: options.UncoverLimit,
- MaxRetry: options.Retries,
- Timeout: options.Timeout,
- RateLimit: uint(options.UncoverRateLimit),
- RateLimitUnit: time.Minute, // default unit is minute
- }
- ch, err := uncover.GetTargetsFromUncover(context.TODO(), options.UncoverField, uncoverOpts)
- if err != nil {
+ callbackFunc := func(k, _ []byte) error {
+ metaInput := &contextargs.MetaInput{}
+ if err := metaInput.Unmarshal(string(k)); err != nil {
return err
}
- for c := range ch {
- i.Set(c)
- }
- }
-
- if len(options.ExcludeTargets) > 0 {
- for _, target := range options.ExcludeTargets {
- switch {
- case iputil.IsCIDR(target):
- ips := expand.CIDR(target)
- i.removeTargets(ips)
- case asn.IsASN(target):
- ips := expand.ASN(target)
- i.removeTargets(ips)
- default:
- i.Del(target)
- }
+ if !callback(metaInput) {
+ return io.EOF
}
+ return nil
}
-
- return nil
-}
-
-// scanInputFromReader scans a line of input from reader and passes it for storage
-func (i *Input) scanInputFromReader(reader io.Reader) {
- scanner := bufio.NewScanner(reader)
- for scanner.Scan() {
- item := scanner.Text()
- switch {
- case iputil.IsCIDR(item):
- ips := expand.CIDR(item)
- i.addTargets(ips)
- case asn.IsASN(item):
- ips := expand.ASN(item)
- i.addTargets(ips)
- default:
- i.Set(item)
- }
+ if i.hostMapStream != nil {
+ _ = i.hostMapStream.Scan(callbackFunc)
+ } else {
+ i.hostMap.Scan(callbackFunc)
}
}
// Set normalizes and stores passed input values
-func (i *Input) Set(value string) {
+func (i *ListInputProvider) Set(value string) {
URL := strings.TrimSpace(value)
if URL == "" {
return
@@ -289,21 +221,138 @@ func (i *Input) Set(value string) {
}
}
+// SetWithProbe only sets the input if it is live
+func (i *ListInputProvider) SetWithProbe(value string, probe providerTypes.InputLivenessProbe) error {
+ probedValue, err := probe.ProbeURL(value)
+ if err != nil {
+ return err
+ }
+ i.Set(probedValue)
+ return nil
+}
+
// SetWithExclusions normalizes and stores passed input values if not excluded
-func (i *Input) SetWithExclusions(value string) {
+func (i *ListInputProvider) SetWithExclusions(value string) error {
URL := strings.TrimSpace(value)
if URL == "" {
- return
+ return nil
}
if i.isExcluded(URL) {
i.skippedCount++
- return
+ return nil
}
i.Set(URL)
+ return nil
+}
+
+// ListInputProvider is a hmap/filekv backed nuclei ListInputProvider provider
+func (i *ListInputProvider) InputType() string {
+ return "ListInputProvider"
+}
+
+// Close closes the input provider
+func (i *ListInputProvider) Close() {
+ i.hostMap.Close()
+ if i.hostMapStream != nil {
+ i.hostMapStream.Close()
+ }
+}
+
+// initializeInputSources initializes the input sources for hmap input
+func (i *ListInputProvider) initializeInputSources(opts *Options) error {
+ options := opts.Options
+
+ // Handle targets flags
+ for _, target := range options.Targets {
+ switch {
+ case iputil.IsCIDR(target):
+ ips := expand.CIDR(target)
+ i.addTargets(ips)
+ case asn.IsASN(target):
+ ips := expand.ASN(target)
+ i.addTargets(ips)
+ default:
+ i.Set(target)
+ }
+ }
+
+ // Handle stdin
+ if options.Stdin {
+ i.scanInputFromReader(readerutil.TimeoutReader{Reader: os.Stdin, Timeout: time.Duration(options.InputReadTimeout)})
+ }
+
+ // Handle target file
+ if options.TargetsFilePath != "" {
+ input, inputErr := os.Open(options.TargetsFilePath)
+ if inputErr != nil {
+ // Handle cloud based input here.
+ if opts.NotFoundCallback == nil || !opts.NotFoundCallback(options.TargetsFilePath) {
+ return errors.Wrap(inputErr, "could not open targets file")
+ }
+ }
+ if input != nil {
+ i.scanInputFromReader(input)
+ input.Close()
+ }
+ }
+ if options.Uncover && options.UncoverQuery != nil {
+ gologger.Info().Msgf("Running uncover query against: %s", strings.Join(options.UncoverEngine, ","))
+ uncoverOpts := &uncoverlib.Options{
+ Agents: options.UncoverEngine,
+ Queries: options.UncoverQuery,
+ Limit: options.UncoverLimit,
+ MaxRetry: options.Retries,
+ Timeout: options.Timeout,
+ RateLimit: uint(options.UncoverRateLimit),
+ RateLimitUnit: time.Minute, // default unit is minute
+ }
+ ch, err := uncover.GetTargetsFromUncover(context.TODO(), options.UncoverField, uncoverOpts)
+ if err != nil {
+ return err
+ }
+ for c := range ch {
+ i.Set(c)
+ }
+ }
+
+ if len(options.ExcludeTargets) > 0 {
+ for _, target := range options.ExcludeTargets {
+ switch {
+ case iputil.IsCIDR(target):
+ ips := expand.CIDR(target)
+ i.removeTargets(ips)
+ case asn.IsASN(target):
+ ips := expand.ASN(target)
+ i.removeTargets(ips)
+ default:
+ i.Del(target)
+ }
+ }
+ }
+
+ return nil
+}
+
+// scanInputFromReader scans a line of input from reader and passes it for storage
+func (i *ListInputProvider) scanInputFromReader(reader io.Reader) {
+ scanner := bufio.NewScanner(reader)
+ for scanner.Scan() {
+ item := scanner.Text()
+ switch {
+ case iputil.IsCIDR(item):
+ ips := expand.CIDR(item)
+ i.addTargets(ips)
+ case asn.IsASN(item):
+ ips := expand.ASN(item)
+ i.addTargets(ips)
+ default:
+ i.Set(item)
+ }
+ }
}
// isExcluded checks if a URL is in the exclusion list
-func (i *Input) isExcluded(URL string) bool {
+func (i *ListInputProvider) isExcluded(URL string) bool {
metaInput := &contextargs.MetaInput{Input: URL}
key, err := metaInput.MarshalString()
if err != nil {
@@ -315,7 +364,7 @@ func (i *Input) isExcluded(URL string) bool {
return exists
}
-func (i *Input) Del(value string) {
+func (i *ListInputProvider) Del(value string) {
URL := strings.TrimSpace(value)
if URL == "" {
return
@@ -398,7 +447,7 @@ func (i *Input) Del(value string) {
}
// setItem in the kv store
-func (i *Input) setItem(metaInput *contextargs.MetaInput) {
+func (i *ListInputProvider) setItem(metaInput *contextargs.MetaInput) {
key, err := metaInput.MarshalString()
if err != nil {
gologger.Warning().Msgf("%s\n", err)
@@ -417,7 +466,7 @@ func (i *Input) setItem(metaInput *contextargs.MetaInput) {
}
// setItem in the kv store
-func (i *Input) delItem(metaInput *contextargs.MetaInput) {
+func (i *ListInputProvider) delItem(metaInput *contextargs.MetaInput) {
targetUrl, err := urlutil.ParseURL(metaInput.Input, true)
if err != nil {
gologger.Warning().Msgf("%s\n", err)
@@ -450,52 +499,20 @@ func (i *Input) delItem(metaInput *contextargs.MetaInput) {
}
// setHostMapStream sets item in stream mode
-func (i *Input) setHostMapStream(data string) {
+func (i *ListInputProvider) setHostMapStream(data string) {
if _, err := i.hostMapStream.Merge([][]byte{[]byte(data)}); err != nil {
gologger.Warning().Msgf("%s\n", err)
return
}
}
-// Count returns the input count
-func (i *Input) Count() int64 {
- return i.inputCount
-}
-
-// Scan iterates the input and each found item is passed to the
-// callback consumer.
-func (i *Input) Scan(callback func(value *contextargs.MetaInput) bool) {
- if i.hostMapStream != nil {
- i.hostMapStreamOnce.Do(func() {
- if err := i.hostMapStream.Process(); err != nil {
- gologger.Warning().Msgf("error in stream mode processing: %s\n", err)
- }
- })
- }
- callbackFunc := func(k, _ []byte) error {
- metaInput := &contextargs.MetaInput{}
- if err := metaInput.Unmarshal(string(k)); err != nil {
- return err
- }
- if !callback(metaInput) {
- return io.EOF
- }
- return nil
- }
- if i.hostMapStream != nil {
- _ = i.hostMapStream.Scan(callbackFunc)
- } else {
- i.hostMap.Scan(callbackFunc)
- }
-}
-
-func (i *Input) addTargets(targets []string) {
+func (i *ListInputProvider) addTargets(targets []string) {
for _, target := range targets {
i.Set(target)
}
}
-func (i *Input) removeTargets(targets []string) {
+func (i *ListInputProvider) removeTargets(targets []string) {
for _, target := range targets {
metaInput := &contextargs.MetaInput{Input: target}
i.delItem(metaInput)
diff --git a/pkg/core/inputs/hybrid/hmap_test.go b/pkg/input/provider/list/hmap_test.go
similarity index 97%
rename from pkg/core/inputs/hybrid/hmap_test.go
rename to pkg/input/provider/list/hmap_test.go
index c11c6efdf6..c3fb4a6699 100644
--- a/pkg/core/inputs/hybrid/hmap_test.go
+++ b/pkg/input/provider/list/hmap_test.go
@@ -1,4 +1,4 @@
-package hybrid
+package list
import (
"net"
@@ -32,7 +32,7 @@ func Test_expandCIDR(t *testing.T) {
for _, tt := range tests {
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
require.Nil(t, err, "could not create temporary input file")
- input := &Input{hostMap: hm}
+ input := &ListInputProvider{hostMap: hm}
ips := expand.CIDR(tt.cidr)
input.addTargets(ips)
@@ -127,7 +127,7 @@ func Test_scanallips_normalizeStoreInputValue(t *testing.T) {
for _, tt := range tests {
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
require.Nil(t, err, "could not create temporary input file")
- input := &Input{
+ input := &ListInputProvider{
hostMap: hm,
ipOptions: &ipOptions{
ScanAllIPs: true,
@@ -169,7 +169,7 @@ func Test_expandASNInputValue(t *testing.T) {
for _, tt := range tests {
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
require.Nil(t, err, "could not create temporary input file")
- input := &Input{hostMap: hm}
+ input := &ListInputProvider{hostMap: hm}
// get the IP addresses for ASN number
ips := expand.ASN(tt.asn)
input.addTargets(ips)
diff --git a/pkg/core/inputs/hybrid/tests/AS134029.txt b/pkg/input/provider/list/tests/AS134029.txt
similarity index 100%
rename from pkg/core/inputs/hybrid/tests/AS134029.txt
rename to pkg/input/provider/list/tests/AS134029.txt
diff --git a/pkg/core/inputs/hybrid/tests/AS14421.txt b/pkg/input/provider/list/tests/AS14421.txt
similarity index 100%
rename from pkg/core/inputs/hybrid/tests/AS14421.txt
rename to pkg/input/provider/list/tests/AS14421.txt
diff --git a/pkg/core/inputs/hybrid/options.go b/pkg/input/provider/list/utils.go
similarity index 83%
rename from pkg/core/inputs/hybrid/options.go
rename to pkg/input/provider/list/utils.go
index 8c7f13af83..221b40a88f 100644
--- a/pkg/core/inputs/hybrid/options.go
+++ b/pkg/input/provider/list/utils.go
@@ -1,4 +1,4 @@
-package hybrid
+package list
type ipOptions struct {
ScanAllIPs bool
diff --git a/pkg/input/provider/simple.go b/pkg/input/provider/simple.go
new file mode 100644
index 0000000000..71b280c2f5
--- /dev/null
+++ b/pkg/input/provider/simple.go
@@ -0,0 +1,73 @@
+package provider
+
+import (
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
+)
+
+// SimpleInputProvider is a simple input provider for nuclei
+// that acts like a No-Op and returns given list of urls as input
+type SimpleInputProvider struct {
+ Inputs []*contextargs.MetaInput
+}
+
+// NewSimpleInputProvider creates a new simple input provider
+func NewSimpleInputProvider() *SimpleInputProvider {
+ return &SimpleInputProvider{
+ Inputs: make([]*contextargs.MetaInput, 0),
+ }
+}
+
+// NewSimpleInputProviderWithUrls creates a new simple input provider with the given urls
+func NewSimpleInputProviderWithUrls(urls ...string) *SimpleInputProvider {
+ provider := NewSimpleInputProvider()
+ for _, url := range urls {
+ provider.Set(url)
+ }
+ return provider
+}
+
+// Count returns the total number of targets for the input provider
+func (s *SimpleInputProvider) Count() int64 {
+ return int64(len(s.Inputs))
+}
+
+// Iterate over all inputs in order
+func (s *SimpleInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {
+ for _, input := range s.Inputs {
+ if !callback(input) {
+ break
+ }
+ }
+}
+
+// Set adds an item to the input provider
+func (s *SimpleInputProvider) Set(value string) {
+ s.Inputs = append(s.Inputs, &contextargs.MetaInput{Input: value})
+}
+
+// SetWithProbe adds an item to the input provider with HTTP probing
+func (s *SimpleInputProvider) SetWithProbe(value string, probe types.InputLivenessProbe) error {
+ probedValue, err := probe.ProbeURL(value)
+ if err != nil {
+ return err
+ }
+ s.Inputs = append(s.Inputs, &contextargs.MetaInput{Input: probedValue})
+ return nil
+}
+
+// SetWithExclusions adds an item to the input provider if it doesn't match any of the exclusions
+func (s *SimpleInputProvider) SetWithExclusions(value string) error {
+ s.Inputs = append(s.Inputs, &contextargs.MetaInput{Input: value})
+ return nil
+}
+
+// InputType returns the type of input provider
+func (s *SimpleInputProvider) InputType() string {
+ return "SimpleInputProvider"
+}
+
+// Close the input provider and cleanup any resources
+func (s *SimpleInputProvider) Close() {
+ // no-op
+}
diff --git a/pkg/input/input.go b/pkg/input/transform.go
similarity index 100%
rename from pkg/input/input.go
rename to pkg/input/transform.go
diff --git a/pkg/input/input_test.go b/pkg/input/transform_test.go
similarity index 100%
rename from pkg/input/input_test.go
rename to pkg/input/transform_test.go
diff --git a/pkg/input/types/http.go b/pkg/input/types/http.go
new file mode 100644
index 0000000000..62b2524976
--- /dev/null
+++ b/pkg/input/types/http.go
@@ -0,0 +1,305 @@
+package types
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/textproto"
+ "strings"
+ "sync"
+
+ "github.com/projectdiscovery/retryablehttp-go"
+ "github.com/projectdiscovery/utils/conversion"
+ mapsutil "github.com/projectdiscovery/utils/maps"
+ urlutil "github.com/projectdiscovery/utils/url"
+)
+
+var (
+ _ json.Marshaler = &RequestResponse{}
+ _ json.Unmarshaler = &RequestResponse{}
+)
+
+// RequestResponse is a struct containing request and response
+// obtained from one of the input formats.
+// this struct can be considered as pd standard for request and response
+type RequestResponse struct {
+ // Timestamp is the timestamp of the request
+ // Timestamp string `json:"timestamp"`
+ // URL is the URL of the request
+ URL urlutil.URL `json:"url"`
+ // Request is the request of the request
+ Request *HttpRequest `json:"request"`
+ // Response is the response of the request
+ Response *HttpResponse `json:"response"`
+
+ // unexported / internal fields
+ // lazy build request
+ req *retryablehttp.Request `json:"-"`
+ reqErr error `json:"-"`
+ once sync.Once `json:"-"`
+}
+
+// Clone clones the request response
+func (rr *RequestResponse) Clone() *RequestResponse {
+ cloned := &RequestResponse{
+ URL: *rr.URL.Clone(),
+ }
+ if rr.Request != nil {
+ cloned.Request = rr.Request.Clone()
+ }
+ if rr.Response != nil {
+ cloned.Response = rr.Response.Clone()
+ }
+ return cloned
+}
+
+// BuildRequest builds a retryablehttp request from the request response
+func (rr *RequestResponse) BuildRequest() (*retryablehttp.Request, error) {
+ rr.once.Do(func() {
+ urlx := rr.URL.Clone()
+ var body io.Reader = nil
+ if rr.Request.Body != "" {
+ body = strings.NewReader(rr.Request.Body)
+ }
+ req, err := retryablehttp.NewRequestFromURL(rr.Request.Method, urlx, body)
+ if err != nil {
+ rr.reqErr = fmt.Errorf("could not create request: %s", err)
+ return
+ }
+ rr.Request.Headers.Iterate(func(k, v string) bool {
+ req.Header.Add(k, v)
+ return true
+ })
+ rr.req = req
+ })
+ return rr.req, rr.reqErr
+}
+
+// To be implemented in the future
+// func (rr *RequestResponse) BuildUnsafeRequest()
+
+// ID returns a unique id/hash for request response
+func (rr *RequestResponse) ID() string {
+ var buff bytes.Buffer
+ buff.WriteString(rr.URL.String())
+ if rr.Request != nil {
+ buff.WriteString(rr.Request.ID())
+ }
+ if rr.Response != nil {
+ buff.WriteString(rr.Response.ID())
+ }
+ val := sha256.Sum256(buff.Bytes())
+ return string(val[:])
+}
+
+// MarshalJSON marshals the request response to json
+func (rr *RequestResponse) MarshalJSON() ([]byte, error) {
+ m := make(map[string]interface{})
+ m["url"] = rr.URL.String()
+ reqBin, err := json.Marshal(rr.Request)
+ if err != nil {
+ return nil, err
+ }
+ m["request"] = reqBin
+ respBin, err := json.Marshal(rr.Response)
+ if err != nil {
+ return nil, err
+ }
+ m["response"] = respBin
+ return json.Marshal(m)
+}
+
+// UnmarshalJSON unmarshals the request response from json
+func (rr *RequestResponse) UnmarshalJSON(data []byte) error {
+ var m map[string]json.RawMessage
+ if err := json.Unmarshal(data, &m); err != nil {
+ return err
+ }
+ urlStr, ok := m["url"]
+ if !ok {
+ return fmt.Errorf("missing url in request response")
+ }
+ parsed, err := urlutil.ParseAbsoluteURL(string(urlStr), false)
+ if err != nil {
+ return err
+ }
+ rr.URL = *parsed
+
+ reqBin, ok := m["request"]
+ if ok {
+ var req HttpRequest
+ if err := json.Unmarshal(reqBin, &req); err != nil {
+ return err
+ }
+ rr.Request = &req
+ }
+
+ respBin, ok := m["response"]
+ if ok {
+ var resp HttpResponse
+ if err := json.Unmarshal(respBin, &resp); err != nil {
+ return err
+ }
+ rr.Response = &resp
+ }
+ return nil
+}
+
+// HttpRequest is a struct containing the http request
+type HttpRequest struct {
+ // method of the request
+ Method string `json:"method"`
+ // headers of the request
+ Headers mapsutil.OrderedMap[string, string] `json:"headers"`
+ // body of the request
+ Body string `json:"body"`
+ // raw request (includes everything including method, headers, body, etc)
+ Raw string `json:"raw"`
+}
+
+// ID returns a unique id/hash for raw request
+func (hr *HttpRequest) ID() string {
+ val := sha256.Sum256([]byte(hr.Raw))
+ return string(val[:])
+}
+
+// Clone clones the request
+func (hr *HttpRequest) Clone() *HttpRequest {
+ return &HttpRequest{
+ Method: hr.Method,
+ Headers: hr.Headers.Clone(),
+ Body: hr.Body,
+ Raw: hr.Raw,
+ }
+}
+
+type HttpResponse struct {
+ // status code of the response
+ StatusCode int `json:"status_code"`
+ // headers of the response
+ Headers mapsutil.OrderedMap[string, string] `json:"headers"`
+ // body of the response
+ Body string `json:"body"`
+ // raw response (includes everything including status code, headers, body, etc)
+ Raw string `json:"raw"`
+}
+
+// Id returns a unique id/hash for raw response
+func (hr *HttpResponse) ID() string {
+ val := sha256.Sum256([]byte(hr.Raw))
+ return string(val[:])
+}
+
+// Clone clones the response
+func (hr *HttpResponse) Clone() *HttpResponse {
+ return &HttpResponse{
+ StatusCode: hr.StatusCode,
+ Headers: hr.Headers.Clone(),
+ Body: hr.Body,
+ Raw: hr.Raw,
+ }
+}
+
+// ParseRawRequest parses a raw request from a string
+// and returns the request and response object
+// Note: it currently does not parse response and is meant to be added manually since its a optional field
+func ParseRawRequest(raw string) (rr *RequestResponse, err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("panic: %v", r)
+ }
+ }()
+ protoReader := textproto.NewReader(bufio.NewReader(strings.NewReader(raw)))
+ methodLine, err := protoReader.ReadLine()
+ if err != nil {
+ return nil, fmt.Errorf("failed to read method line: %s", err)
+ }
+ rr = &RequestResponse{
+ Request: &HttpRequest{},
+ }
+ /// must contain at least 3 parts
+ parts := strings.Split(methodLine, " ")
+ if len(parts) < 3 {
+ return nil, fmt.Errorf("invalid method line: %s", methodLine)
+ }
+ method := parts[0]
+ rr.Request.Method = method
+
+ // parse relative url
+ urlx, err := urlutil.ParseRawRelativePath(parts[1], true)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse url: %s", err)
+ }
+ rr.URL = *urlx
+
+ // parse host line
+ hostLine, err := protoReader.ReadLine()
+ if err != nil {
+ return nil, fmt.Errorf("failed to read host line: %s", err)
+ }
+ sep := strings.Index(hostLine, ":")
+ if sep <= 0 || sep >= len(hostLine)-1 {
+ return nil, fmt.Errorf("invalid host line: %s", hostLine)
+ }
+ hostLine = hostLine[sep+2:]
+ rr.URL.Host = hostLine
+
+ // parse headers
+ rr.Request.Headers = mapsutil.NewOrderedMap[string, string]()
+ for {
+ line, err := protoReader.ReadLine()
+ if err != nil {
+ return nil, fmt.Errorf("failed to read header line: %s", err)
+ }
+ if line == "" {
+ // end of headers next is body
+ break
+ }
+ parts := strings.SplitN(line, ":", 2)
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("invalid header line: %s", line)
+ }
+ rr.Request.Headers.Set(parts[0], parts[1][1:])
+ }
+
+ // parse body
+ rr.Request.Body = ""
+ var buff bytes.Buffer
+ _, err = buff.ReadFrom(protoReader.R)
+ if err != nil && err != io.EOF {
+ return nil, fmt.Errorf("failed to read body: %s", err)
+ }
+ if buff.Len() > 0 {
+ // yaml may include trailing newlines
+ // remove them if present
+ bin := buff.Bytes()
+ if bin[len(bin)-1] == '\n' {
+ bin = bin[:len(bin)-1]
+ }
+ if bin[len(bin)-1] == '\r' || bin[len(bin)-1] == '\n' {
+ bin = bin[:len(bin)-1]
+ }
+ rr.Request.Body = conversion.String(bin)
+ }
+
+ // set raw request
+ rr.Request.Raw = raw
+ return rr, nil
+}
+
+// ParseRawRequestWithURL parses a raw request from a string with given url
+func ParseRawRequestWithURL(raw, url string) (rr *RequestResponse, err error) {
+ rr, err = ParseRawRequest(raw)
+ if err != nil {
+ return nil, err
+ }
+ urlx, err := urlutil.ParseAbsoluteURL(url, false)
+ if err != nil {
+ return nil, err
+ }
+ rr.URL = *urlx
+ return rr, nil
+}
diff --git a/pkg/input/types/http_test.go b/pkg/input/types/http_test.go
new file mode 100644
index 0000000000..d674c56353
--- /dev/null
+++ b/pkg/input/types/http_test.go
@@ -0,0 +1,67 @@
+package types
+
+import (
+ "io"
+ "net/http"
+ "net/http/httputil"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+// Possibly add more tests here.
+func TestParseHttpRequest(t *testing.T) {
+ tests := []struct {
+ name string
+ method string
+ url string
+ headerKey string
+ headerValue string
+ body string
+ contentLength string
+ }{
+ {"GET Request", "GET", "example.com/", "X-Test", "test", "", "0"},
+ {"POST Request with body", "POST", "example.com/resource", "Content-Type", "application/json", `{"key":"value"}`, "15"},
+ {"PUT Request with body", "PUT", "example.com/update", "Content-Type", "text/plain", "update data", "11"},
+ {"DELETE Request", "DELETE", "example.com/delete", "X-User", "user1", "", "0"},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ var bodyReader io.Reader
+ if tc.body != "" {
+ bodyReader = strings.NewReader(tc.body)
+ }
+ req, err := http.NewRequest(tc.method, "http://"+tc.url, bodyReader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ req.Header.Add(tc.headerKey, tc.headerValue)
+ if tc.contentLength != "" {
+ req.Header.Add("Content-Length", tc.contentLength)
+ }
+ binx, err := httputil.DumpRequestOut(req, true)
+ if err != nil {
+ t.Fatal(err)
+ }
+ rr, err := ParseRawRequest(string(binx))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if rr.Request.Method != tc.method {
+ t.Fatalf("invalid method: got %v want %v", rr.Request.Method, tc.method)
+ }
+ require.Equal(t, tc.url, rr.URL.String())
+ val, _ := rr.Request.Headers.Get(tc.headerKey)
+ require.Equal(t, tc.headerValue, val)
+ if tc.body != "" {
+ require.Equal(t, tc.body, rr.Request.Body)
+ contentLengthVal, _ := rr.Request.Headers.Get("Content-Length")
+ require.Equal(t, tc.contentLength, contentLengthVal)
+ }
+
+ t.Log(*rr.Request)
+ })
+ }
+}
diff --git a/pkg/input/types/probe.go b/pkg/input/types/probe.go
new file mode 100644
index 0000000000..006faada94
--- /dev/null
+++ b/pkg/input/types/probe.go
@@ -0,0 +1,7 @@
+package types
+
+// InputLivenessProbe is an interface for probing the liveness of an input
+type InputLivenessProbe interface {
+ // ProbeURL probes the scheme for a URL. first HTTPS is tried
+ ProbeURL(input string) (string, error)
+}
diff --git a/pkg/installer/util.go b/pkg/installer/util.go
index c0f7520de3..af74826216 100644
--- a/pkg/installer/util.go
+++ b/pkg/installer/util.go
@@ -103,29 +103,3 @@ func isEmptyDir(dir string) bool {
})
return !hasFiles
}
-
-// getUtmSource returns utm_source from environment variable
-func getUtmSource() string {
- value := ""
- switch {
- case os.Getenv("GH_ACTION") != "":
- value = "ghci"
- case os.Getenv("TRAVIS") != "":
- value = "travis"
- case os.Getenv("CIRCLECI") != "":
- value = "circleci"
- case os.Getenv("CI") != "":
- value = "gitlabci" // this also includes bitbucket
- case os.Getenv("GITHUB_ACTIONS") != "":
- value = "ghci"
- case os.Getenv("AWS_EXECUTION_ENV") != "":
- value = os.Getenv("AWS_EXECUTION_ENV")
- case os.Getenv("JENKINS_URL") != "":
- value = "jenkins"
- case os.Getenv("FUNCTION_TARGET") != "":
- value = "gcf"
- default:
- value = "unknown"
- }
- return value
-}
diff --git a/pkg/installer/versioncheck.go b/pkg/installer/versioncheck.go
index 54d661885e..d8e1ae5251 100644
--- a/pkg/installer/versioncheck.go
+++ b/pkg/installer/versioncheck.go
@@ -5,9 +5,9 @@ import (
"io"
"net/url"
"os"
- "runtime"
"sync"
+ "github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/retryablehttp-go"
updateutils "github.com/projectdiscovery/utils/update"
@@ -55,16 +55,15 @@ func NucleiSDKVersionCheck() {
// 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)
+ values, err := url.ParseQuery(updateutils.GetpdtmParams(config.Version))
+ if err != nil {
+ gologger.Verbose().Msgf("error parsing update check params: %v", err)
+ return updateutils.GetpdtmParams(config.Version)
+ }
if isSDK {
- params.Add("sdk", "true")
+ values.Add("sdk", "true")
}
- params.Add("utm_source", getUtmSource())
- return params.Encode()
+ return values.Encode()
}
// UpdateIgnoreFile updates default ignore file by downloading latest ignore file
diff --git a/pkg/js/devtools/README.md b/pkg/js/devtools/README.md
index 373e4b92ce..65248c2d8a 100644
--- a/pkg/js/devtools/README.md
+++ b/pkg/js/devtools/README.md
@@ -6,9 +6,6 @@ devtools contains tools and scripts to automate booring tasks related to javascr
[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
diff --git a/pkg/js/devtools/bindgen/cmd/bindgen/main.go b/pkg/js/devtools/bindgen/cmd/bindgen/main.go
index cb849c76fc..6233f3975a 100644
--- a/pkg/js/devtools/bindgen/cmd/bindgen/main.go
+++ b/pkg/js/devtools/bindgen/cmd/bindgen/main.go
@@ -52,10 +52,12 @@ func process() error {
}
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)
- }
+ // if !goOnly {
+ // 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)
diff --git a/pkg/js/devtools/bindgen/generator.go b/pkg/js/devtools/bindgen/generator.go
index 8c471e35a2..313c6d41ca 100644
--- a/pkg/js/devtools/bindgen/generator.go
+++ b/pkg/js/devtools/bindgen/generator.go
@@ -28,16 +28,18 @@ var (
// 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
+ PackageName string
+ PackagePath string
+ HasObjects bool
+ 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
+ PackageDefinedConstructor map[string]struct{}
typesPackage *types.Package
@@ -68,16 +70,17 @@ type PackageFunctionExtra struct {
// 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),
+ 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),
+ PackageDefinedConstructor: make(map[string]struct{}),
}
}
@@ -144,6 +147,24 @@ func CreateTemplateData(directory string, packagePrefix string) (*TemplateData,
delete(data.PackageFuncsExtra, item)
}
}
+
+ // map types with corresponding constructors
+ for constructor := range data.PackageDefinedConstructor {
+ object:
+ for k := range data.PackageTypes {
+ if strings.Contains(constructor, k) {
+ data.PackageTypes[k] = constructor
+ break object
+ }
+ }
+ }
+ for k, v := range data.PackageTypes {
+ if k == v || v == "" {
+ data.HasObjects = true
+ data.PackageTypes[k] = ""
+ }
+ }
+
return data, nil
}
@@ -345,6 +366,10 @@ func (d *TemplateData) handleStarExpr(v *ast.StarExpr) string {
}
func (d *TemplateData) collectTypeFromExternal(pkg *types.Package, pkgName, name string) {
+ if pkgName == "goja" {
+ // no need to attempt to collect types from goja ( this is metadata )
+ return
+ }
extra := PackageTypeExtra{
Fields: make(map[string]string),
}
@@ -395,11 +420,21 @@ func (d *TemplateData) collectFuncDecl(decl *ast.FuncDecl) (extra PackageFunctio
extra.Name = decl.Name.Name
extra.Doc = convertCommentsToJavascript(decl.Doc.Text())
+ isConstructor := false
+
for _, arg := range decl.Type.Params.List {
+ p := exprToString(arg.Type)
+ if strings.Contains(p, "goja.ConstructorCall") {
+ isConstructor = true
+ }
for _, name := range arg.Names {
extra.Args = append(extra.Args, name.Name)
}
}
+ if isConstructor {
+ d.PackageDefinedConstructor[decl.Name.Name] = struct{}{}
+ }
+
extra.Returns = d.extractReturns(decl)
return extra
}
@@ -409,3 +444,22 @@ func convertCommentsToJavascript(comments string) string {
suffix := strings.Trim(strings.TrimSuffix(strings.ReplaceAll(comments, "\n", "\n// "), "// "), "\n")
return fmt.Sprintf("// %s", suffix)
}
+
+// exprToString converts an expression to a string
+func exprToString(expr ast.Expr) string {
+ switch t := expr.(type) {
+ case *ast.Ident:
+ return t.Name
+ case *ast.SelectorExpr:
+ return exprToString(t.X) + "." + t.Sel.Name
+ case *ast.StarExpr:
+ return exprToString(t.X)
+ case *ast.ArrayType:
+ return "[]" + exprToString(t.Elt)
+ case *ast.InterfaceType:
+ return "interface{}"
+ // Add more cases to handle other types
+ default:
+ return fmt.Sprintf("%T", expr)
+ }
+}
diff --git a/pkg/js/devtools/bindgen/templates/go_class.tmpl b/pkg/js/devtools/bindgen/templates/go_class.tmpl
index 74d52b3e02..ede5404719 100644
--- a/pkg/js/devtools/bindgen/templates/go_class.tmpl
+++ b/pkg/js/devtools/bindgen/templates/go_class.tmpl
@@ -27,18 +27,15 @@ func init() {
"{{$objName}}": {{$pkgName}}.{{$objDefine}},
{{- end}}
- // Types (value type)
+ // Objects / Classes
{{- range $objName, $objDefine := .PackageTypes}}
- "{{$objName}}": {{printf "func() %s.%s { return %s.%s{} }" $pkgName $objDefine $pkgName $objDefine}},
+ {{- if $objDefine}}
+ "{{$objName}}": {{$pkgName}}.{{$objDefine}},
+ {{- else}}
+ "{{$objName}}": gojs.GetClassConstructor[{{$pkgName}}.{{$objName}}](&{{$pkgName}}.{{$objName}}{}),
+ {{- end}}
{{- 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()
}
diff --git a/pkg/js/devtools/jsdocgen/README.md b/pkg/js/devtools/jsdocgen/README.md
deleted file mode 100644
index 10bf824173..0000000000
--- a/pkg/js/devtools/jsdocgen/README.md
+++ /dev/null
@@ -1,116 +0,0 @@
-## 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/pkg/js/devtools/jsdocgen/main.go b/pkg/js/devtools/jsdocgen/main.go
deleted file mode 100644
index 124b1fd69c..0000000000
--- a/pkg/js/devtools/jsdocgen/main.go
+++ /dev/null
@@ -1,178 +0,0 @@
-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/pkg/js/devtools/tsgen/README.md b/pkg/js/devtools/tsgen/README.md
new file mode 100644
index 0000000000..fe404913c5
--- /dev/null
+++ b/pkg/js/devtools/tsgen/README.md
@@ -0,0 +1,3 @@
+# tsgen
+
+tsgen is devtool to generate dummy typescript code for goja node modules written in golang. These generated typescript code can be compiled to generate .d.ts files to provide intellisense to editors like vscode and documentation for the node modules.
\ No newline at end of file
diff --git a/pkg/js/devtools/tsgen/astutil.go b/pkg/js/devtools/tsgen/astutil.go
new file mode 100644
index 0000000000..72e7b1bc8b
--- /dev/null
+++ b/pkg/js/devtools/tsgen/astutil.go
@@ -0,0 +1,109 @@
+package tsgen
+
+import (
+ "fmt"
+ "go/ast"
+ "strings"
+)
+
+// isExported checks if the given name is exported
+func isExported(name string) bool {
+ return ast.IsExported(name)
+}
+
+// exprToString converts an expression to a string
+func exprToString(expr ast.Expr) string {
+ switch t := expr.(type) {
+ case *ast.Ident:
+ return toTsTypes(t.Name)
+ case *ast.SelectorExpr:
+ return exprToString(t.X) + "." + t.Sel.Name
+ case *ast.StarExpr:
+ return exprToString(t.X)
+ case *ast.ArrayType:
+ return toTsTypes("[]" + exprToString(t.Elt))
+ case *ast.InterfaceType:
+ return "interface{}"
+ case *ast.MapType:
+ return "Record<" + toTsTypes(exprToString(t.Key)) + ", " + toTsTypes(exprToString(t.Value)) + ">"
+ // Add more cases to handle other types
+ default:
+ return fmt.Sprintf("%T", expr)
+ }
+}
+
+// toTsTypes converts Go types to TypeScript types
+func toTsTypes(t string) string {
+ if strings.Contains(t, "interface{}") {
+ return "any"
+ }
+ if strings.HasPrefix(t, "map[") {
+ return convertMaptoRecord(t)
+ }
+ switch t {
+ case "string":
+ return "string"
+ case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
+ return "number"
+ case "float32", "float64":
+ return "number"
+ case "bool":
+ return "boolean"
+ case "[]byte":
+ return "Uint8Array"
+ case "interface{}":
+ return "any"
+ case "time.Duration":
+ return "number"
+ case "time.Time":
+ return "Date"
+ default:
+ if strings.HasPrefix(t, "[]") {
+ return toTsTypes(strings.TrimPrefix(t, "[]")) + "[]"
+ }
+ return t
+ }
+}
+
+func TsDefaultValue(t string) string {
+ switch t {
+ case "string":
+ return `""`
+ case "number":
+ return `0`
+ case "boolean":
+ return `false`
+ case "Uint8Array":
+ return `new Uint8Array(8)`
+ case "any":
+ return `undefined`
+ case "interface{}":
+ return `undefined`
+ default:
+ if strings.Contains(t, "[]") {
+ return `[]`
+ }
+ return "new " + t + "()"
+ }
+}
+
+// Ternary is a ternary operator for strings
+func Ternary(condition bool, trueVal, falseVal string) string {
+ if condition {
+ return trueVal
+ }
+ return falseVal
+}
+
+// checkCanFail checks if a function can fail
+func checkCanFail(fn *ast.FuncDecl) bool {
+ if fn.Type.Results != nil {
+ for _, result := range fn.Type.Results.List {
+ // Check if any of the return types is an error
+ if ident, ok := result.Type.(*ast.Ident); ok && ident.Name == "error" {
+ return true
+ }
+ }
+ }
+ return false
+}
diff --git a/pkg/js/devtools/tsgen/cmd/tsgen/main.go b/pkg/js/devtools/tsgen/cmd/tsgen/main.go
new file mode 100644
index 0000000000..5296c11933
--- /dev/null
+++ b/pkg/js/devtools/tsgen/cmd/tsgen/main.go
@@ -0,0 +1,118 @@
+package main
+
+import (
+ "bytes"
+ _ "embed"
+ "flag"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "text/template"
+
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/js/devtools/tsgen"
+ fileutil "github.com/projectdiscovery/utils/file"
+)
+
+// Define your template
+//
+//go:embed tsmodule.go.tmpl
+var tsTemplate string
+
+var (
+ source string
+ out string
+)
+
+func main() {
+ flag.StringVar(&source, "dir", "", "Directory to parse")
+ flag.StringVar(&out, "out", "src", "Typescript files Output directory")
+ flag.Parse()
+
+ // Create an instance of the template
+ tmpl := template.New("ts")
+ tmpl = tmpl.Funcs(template.FuncMap{
+ "splitLines": func(s string) []string {
+ tmp := strings.Split(s, "\n")
+ filtered := []string{}
+ for _, line := range tmp {
+ if strings.TrimSpace(line) != "" {
+ filtered = append(filtered, line)
+ }
+ }
+ return filtered
+ },
+ })
+ var err error
+ tmpl, err = tmpl.Parse(tsTemplate)
+ if err != nil {
+ panic(err)
+ }
+
+ // Create the output directory
+ _ = fileutil.CreateFolder(out)
+
+ dirs := []string{}
+ _ = filepath.WalkDir(source, func(path string, d os.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ // only load module directory skip root directory
+ if d.IsDir() {
+ files, _ := os.ReadDir(path)
+ for _, file := range files {
+ if !file.IsDir() && strings.HasSuffix(file.Name(), ".go") {
+ dirs = append(dirs, path)
+ break
+ }
+ }
+ }
+ return nil
+ })
+
+ // walk each directory
+ for _, dir := range dirs {
+ entityList := []tsgen.Entity{}
+ ep, err := tsgen.NewEntityParser(dir)
+ if err != nil {
+ panic(fmt.Errorf("could not create entity parser: %s", err))
+ }
+ if err := ep.Parse(); err != nil {
+ panic(fmt.Errorf("could not parse entities: %s", err))
+ }
+ entityList = append(entityList, ep.GetEntities()...)
+ entityList = sortEntities(entityList)
+ var buff bytes.Buffer
+ err = tmpl.Execute(&buff, entityList)
+ if err != nil {
+ panic(err)
+ }
+ moduleName := filepath.Base(dir)
+ gologger.Info().Msgf("Writing %s.ts", moduleName)
+ // create appropriate directory if missing
+ // _ = fileutil.CreateFolder(filepath.Join(out, moduleName))
+ _ = os.WriteFile(filepath.Join(out, moduleName)+".ts", buff.Bytes(), 0755)
+ }
+
+ // generating index.ts file
+ var buff bytes.Buffer
+ for _, dir := range dirs {
+ buff.WriteString(fmt.Sprintf("export * as %s from './%s';\n", filepath.Base(dir), filepath.Base(dir)))
+ }
+ _ = os.WriteFile(filepath.Join(out, "index.ts"), buff.Bytes(), 0755)
+}
+
+func sortEntities(entities []tsgen.Entity) []tsgen.Entity {
+ sort.Slice(entities, func(i, j int) bool {
+ if entities[i].Type != entities[j].Type {
+ // Define the order of types
+ order := map[string]int{"function": 1, "class": 2, "interface": 3}
+ return order[entities[i].Type] < order[entities[j].Type]
+ }
+ // If types are the same, sort by name
+ return entities[i].Name < entities[j].Name
+ })
+ return entities
+}
diff --git a/pkg/js/devtools/tsgen/cmd/tsgen/tsmodule.go.tmpl b/pkg/js/devtools/tsgen/cmd/tsgen/tsmodule.go.tmpl
new file mode 100644
index 0000000000..ff223e65bb
--- /dev/null
+++ b/pkg/js/devtools/tsgen/cmd/tsgen/tsmodule.go.tmpl
@@ -0,0 +1,79 @@
+{{- range .}}
+
+{{ if eq .Type "const" -}}
+{{ if .Description }}/** {{ .Description }} */{{- end }}
+export const {{ .Name }} = {{ .Value }};
+{{- else if eq .Type "class" -}}
+/**
+{{- range (splitLines .Description) }}
+ * {{ . }}
+{{- end }}
+ */
+export class {{ .Name }} {
+ {{"\n"}}
+ {{- range $property := .Class.Properties }}
+ {{ if .Description }}
+ /**
+ {{- range (splitLines $property.Description) }}
+ * {{ . }}
+ {{- end }}
+ */
+ {{ end }}
+ public {{ $property.Name }}?: {{ $property.Type }};
+ {{"\n"}}
+ {{- end }}
+ // Constructor of {{ .Name}}
+ {{- if eq (len .Class.Constructor.Parameters) 0 }}
+ constructor() {}
+ {{- else }}
+ constructor(
+ {{- range $index, $param := .Class.Constructor.Parameters }}
+ {{- if $index }}, {{ end }}{{ $param.Name }}: {{ $param.Type }}
+ {{- end }} ) {}
+ {{"\n"}}
+ {{- end }}
+
+ {{- range $method := .Class.Methods }}
+ /**
+ {{- range (splitLines $method.Description) }}
+ * {{ . }}
+ {{- end }}
+ */
+ public {{ $method.Name }}({{ range $index, $element := $method.Parameters }}{{ if $index }}, {{ end }}{{ $element.Name }}: {{ $element.Type }}{{ end }}): {{ $method.Returns }} {
+ {{ $method.ReturnStmt }}
+ }
+ {{"\n"}}
+ {{- end }}
+}
+
+{{ else if eq .Type "function" -}}
+/**
+{{- range (splitLines .Description) }}
+ * {{ . }}
+{{- end }}
+ */
+export function {{ .Name }}({{ range $index, $element := .Function.Parameters }}{{ if $index }}, {{ end }}{{ $element.Name }}: {{ $element.Type }}{{ end }}): {{ .Function.Returns }} {
+ {{ .Function.ReturnStmt }}
+}
+
+{{ else if eq .Type "interface" -}}
+/**
+{{- range (splitLines .Description) }}
+ * {{ . }}
+{{- end }}
+ */
+export interface {{ .Name }} {
+ {{- range $property := .Object.Properties }}
+ {{ if .Description }}
+ /**
+ {{- range (splitLines .Description) }}
+ * {{ . }}
+ {{- end }}
+ */
+ {{ end }}
+ {{ $property.Name }}?: {{ $property.Type }},
+ {{- end }}
+}
+
+{{ end }}
+{{- end }}
\ No newline at end of file
diff --git a/pkg/js/devtools/tsgen/parser.go b/pkg/js/devtools/tsgen/parser.go
new file mode 100644
index 0000000000..e285b5f007
--- /dev/null
+++ b/pkg/js/devtools/tsgen/parser.go
@@ -0,0 +1,585 @@
+package tsgen
+
+import (
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "regexp"
+ "strings"
+
+ "github.com/projectdiscovery/gologger"
+ sliceutil "github.com/projectdiscovery/utils/slice"
+ "golang.org/x/tools/go/packages"
+)
+
+// EntityParser is responsible for parsing a go file and generating
+// corresponding typescript entities.
+type EntityParser struct {
+ syntax []*ast.File
+ structTypes map[string]Entity
+ imports map[string]*packages.Package
+ newObjects map[string]*Entity // new objects to create from external packages
+ vars []Entity
+ entities []Entity
+}
+
+// NewEntityParser creates a new EntityParser
+func NewEntityParser(dir string) (*EntityParser, error) {
+
+ cfg := &packages.Config{
+ Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports |
+ packages.NeedTypes | packages.NeedSyntax | packages.NeedTypes |
+ packages.NeedModule | packages.NeedTypesInfo,
+ Tests: false,
+ Dir: dir,
+ ParseFile: func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
+ return parser.ParseFile(fset, filename, src, parser.ParseComments)
+ },
+ }
+ pkgs, err := packages.Load(cfg, ".")
+ if err != nil {
+ return nil, err
+ }
+ if len(pkgs) == 0 {
+ return nil, errors.New("no packages found")
+ }
+ pkg := pkgs[0]
+
+ return &EntityParser{
+ syntax: pkg.Syntax,
+ structTypes: map[string]Entity{},
+ imports: map[string]*packages.Package{},
+ newObjects: map[string]*Entity{},
+ }, nil
+}
+
+func (p *EntityParser) GetEntities() []Entity {
+ return p.entities
+}
+
+// Parse parses the given file and generates corresponding typescript entities
+func (p *EntityParser) Parse() error {
+ p.extractVarsNConstants()
+ // extract all struct types from the AST
+ p.extractStructTypes()
+ // load all imported packages
+ if err := p.loadImportedPackages(); err != nil {
+ return err
+ }
+
+ for _, file := range p.syntax {
+ // Traverse the AST and find all relevant declarations
+ ast.Inspect(file, func(n ast.Node) bool {
+ // look for funtions and methods
+ // and generate entities for them
+ fn, ok := n.(*ast.FuncDecl)
+ if ok {
+ if !isExported(fn.Name.Name) {
+ return false
+ }
+ entity, err := p.extractFunctionFromNode(fn)
+ if err != nil {
+ gologger.Error().Msgf("Could not extract function %s: %s\n", fn.Name.Name, err)
+ return false
+ }
+
+ if entity.IsConstructor {
+ // add this to the list of entities
+ p.entities = append(p.entities, entity)
+ return false
+ }
+
+ // check if function has a receiver
+ if fn.Recv != nil {
+ // get the name of the receiver
+ receiverName := exprToString(fn.Recv.List[0].Type)
+ // check if the receiver is a struct
+ if _, ok := p.structTypes[receiverName]; ok {
+ // add the method to the class
+ method := Method{
+ Name: entity.Name,
+ Description: strings.ReplaceAll(entity.Description, "Function", "Method"),
+ Parameters: entity.Function.Parameters,
+ Returns: entity.Function.Returns,
+ CanFail: entity.Function.CanFail,
+ ReturnStmt: entity.Function.ReturnStmt,
+ }
+
+ // add this method to corresponding class
+ allMethods := p.structTypes[receiverName].Class.Methods
+ if allMethods == nil {
+ allMethods = []Method{}
+ }
+ entity = p.structTypes[receiverName]
+ entity.Class.Methods = append(allMethods, method)
+ p.structTypes[receiverName] = entity
+ return false
+ }
+ }
+ // add the function to the list of global entities
+ p.entities = append(p.entities, entity)
+ return false
+ }
+
+ return true
+ })
+ }
+
+ for _, file := range p.syntax {
+ ast.Inspect(file, func(n ast.Node) bool {
+ // logic here to extract all fields and methods from a struct
+ // and add them to the entities slice
+ // TODO: we only support structs and not type aliases
+ typeSpec, ok := n.(*ast.TypeSpec)
+ if ok {
+ if !isExported(typeSpec.Name.Name) {
+ return false
+ }
+ structType, ok := typeSpec.Type.(*ast.StructType)
+ if !ok {
+ // This is not a struct, so continue traversing the AST
+ return false
+ }
+ entity := Entity{
+ Name: typeSpec.Name.Name,
+ Type: "class",
+ Description: Ternary(strings.TrimSpace(typeSpec.Doc.Text()) != "", typeSpec.Doc.Text(), typeSpec.Name.Name+" Class"),
+ Class: Class{
+ Properties: p.extractClassProperties(structType),
+ },
+ }
+ // map struct name to entity and create a new entity if doesn't exist
+ if _, ok := p.structTypes[typeSpec.Name.Name]; ok {
+ entity.Class.Methods = p.structTypes[typeSpec.Name.Name].Class.Methods
+ entity.Description = p.structTypes[typeSpec.Name.Name].Description
+ p.structTypes[typeSpec.Name.Name] = entity
+ } else {
+ p.structTypes[typeSpec.Name.Name] = entity
+ }
+ return false
+ }
+ // Continue traversing the AST
+ return true
+ })
+ }
+
+ // add all struct types to the list of global entities
+ for k, v := range p.structTypes {
+ if v.Type == "class" && len(v.Class.Methods) > 0 {
+ p.entities = append(p.entities, v)
+ } else if v.Type == "class" && len(v.Class.Methods) == 0 {
+ if k == "Object" {
+ continue
+ }
+ entity := Entity{
+ Name: k,
+ Type: "interface",
+ Description: strings.TrimSpace(strings.ReplaceAll(v.Description, "Class", "interface")),
+ Object: Interface{
+ Properties: v.Class.Properties,
+ },
+ }
+ p.entities = append(p.entities, entity)
+ }
+ }
+
+ // handle external structs
+ for k := range p.newObjects {
+ // if k == "Object" {
+ // continue
+ // }
+ if err := p.scrapeAndCreate(k); err != nil {
+ return fmt.Errorf("could not scrape and create new object: %s", err)
+ }
+ }
+
+ interfaceList := map[string]struct{}{}
+ for _, v := range p.entities {
+ if v.Type == "interface" {
+ interfaceList[v.Name] = struct{}{}
+ }
+ }
+
+ // handle method return types
+ for index, v := range p.entities {
+ if len(v.Class.Methods) > 0 {
+ for i, method := range v.Class.Methods {
+ if !strings.Contains(method.Returns, "null") {
+ x := strings.TrimSpace(method.Returns)
+ if _, ok := interfaceList[x]; ok {
+ // non nullable interface return type detected
+ method.Returns = x + " | null"
+ method.ReturnStmt = "return null;"
+ p.entities[index].Class.Methods[i] = method
+ }
+ }
+ }
+ }
+ }
+
+ // handle constructors
+ for _, v := range p.entities {
+ if v.IsConstructor {
+
+ // correlate it with the class
+ foundStruct:
+ for i, class := range p.entities {
+ if class.Type != "class" {
+ continue foundStruct
+ }
+ if strings.Contains(v.Name, class.Name) {
+ // add constructor to the class
+ p.entities[i].Class.Constructor = v.Function
+ break foundStruct
+ }
+ }
+ }
+ }
+
+ filtered := []Entity{}
+ for _, v := range p.entities {
+ if !v.IsConstructor {
+ filtered = append(filtered, v)
+ }
+ }
+
+ // add all vars and constants
+ filtered = append(filtered, p.vars...)
+
+ p.entities = filtered
+ return nil
+}
+
+// extractPropertiesFromStruct extracts all properties from the given struct
+func (p *EntityParser) extractClassProperties(node *ast.StructType) []Property {
+ var properties []Property
+
+ for _, field := range node.Fields.List {
+ // Skip unexported fields
+ if len(field.Names) > 0 && !field.Names[0].IsExported() {
+ continue
+ }
+
+ // Get the type of the field as a string
+ typeString := exprToString(field.Type)
+
+ // If the field is anonymous (embedded), use the type name as the field name
+ if len(field.Names) == 0 {
+ if ident, ok := field.Type.(*ast.Ident); ok {
+ properties = append(properties, Property{
+ Name: ident.Name,
+ Type: typeString,
+ Description: field.Doc.Text(),
+ })
+ }
+ continue
+ }
+
+ // Iterate through all names (for multiple names in a single declaration)
+ for _, fieldName := range field.Names {
+ // Only consider exported fields
+ if fieldName.IsExported() {
+ property := Property{
+ Name: fieldName.Name,
+ Type: typeString,
+ Description: field.Doc.Text(),
+ }
+ if strings.Contains(property.Type, ".") {
+ // external struct found
+ property.Type = p.handleExternalStruct(property.Type)
+ }
+ properties = append(properties, property)
+ }
+ }
+ }
+ return properties
+}
+
+var (
+ constructorRe = `(constructor\([^)]*\))`
+ constructorReCompiled = regexp.MustCompile(constructorRe)
+)
+
+// extractFunctionFromNode extracts a function from the given AST node
+func (p *EntityParser) extractFunctionFromNode(fn *ast.FuncDecl) (Entity, error) {
+ entity := Entity{
+ Name: fn.Name.Name,
+ Type: "function",
+ Description: Ternary(strings.TrimSpace(fn.Doc.Text()) != "", fn.Doc.Text(), fn.Name.Name+" Function"),
+ Function: Function{
+ Parameters: p.extractParameters(fn),
+ Returns: p.extractReturnType(fn),
+ CanFail: checkCanFail(fn),
+ },
+ }
+ // check if it is a constructor
+ if strings.Contains(entity.Function.Returns, "Object") && len(entity.Function.Parameters) == 2 {
+ // this is a constructor defined that accepts something as input
+ // get constructor signature from comments
+ constructorSig := constructorReCompiled.FindString(entity.Description)
+ entity.IsConstructor = true
+ entity.Function = updateFuncWithConstructorSig(constructorSig, entity.Function)
+ return entity, nil
+ }
+
+ // fix/adjust return statement
+ if entity.Function.Returns == "void" {
+ entity.Function.ReturnStmt = "return;"
+ } else if strings.Contains(entity.Function.Returns, "null") {
+ entity.Function.ReturnStmt = "return null;"
+ } else if fn.Recv != nil && exprToString(fn.Recv.List[0].Type) == entity.Function.Returns {
+ entity.Function.ReturnStmt = "return this;"
+ } else {
+ entity.Function.ReturnStmt = "return " + TsDefaultValue(entity.Function.Returns) + ";"
+ }
+ return entity, nil
+}
+
+// extractReturnType extracts the return type from the given function
+func (p *EntityParser) extractReturnType(fn *ast.FuncDecl) (out string) {
+ defer func() {
+ if out == "" {
+ out = "void"
+ }
+ if strings.Contains(out, "interface{}") {
+ out = strings.ReplaceAll(out, "interface{}", "any")
+ }
+ }()
+ var returns []string
+ if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 {
+ for _, result := range fn.Type.Results.List {
+ tmp := exprToString(result.Type)
+ if strings.Contains(tmp, ".") && !strings.HasPrefix(tmp, "goja.") {
+ tmp = p.handleExternalStruct(tmp) + " | null" // external object interfaces can always return null
+ }
+ returns = append(returns, tmp)
+ }
+ }
+ if len(returns) == 1 {
+ val := returns[0]
+ val = strings.TrimPrefix(val, "*")
+ if val == "error" {
+ out = "void"
+ } else {
+ out = val
+ }
+ return
+ }
+ if len(returns) > 1 {
+ // in goja we stick 2 only 2 values with one being error
+ for _, val := range returns {
+ val = strings.TrimPrefix(val, "*")
+ if val != "error" {
+ out = val
+ break
+ }
+ }
+ if sliceutil.Contains(returns, "error") {
+ // add | null to the return type
+ out = out + " | null"
+ return
+ }
+ }
+ return "void"
+}
+
+// example: Map[string][]string -> Record
+func convertMaptoRecord(input string) (out string) {
+ var key, value string
+ input = strings.TrimPrefix(input, "Map[")
+ key = input[:strings.Index(input, "]")]
+ value = input[strings.Index(input, "]")+1:]
+ return "Record<" + toTsTypes(key) + ", " + toTsTypes(value) + ">"
+}
+
+// extractParameters extracts all parameters from the given function
+func (p *EntityParser) extractParameters(fn *ast.FuncDecl) []Parameter {
+ var parameters []Parameter
+ for _, param := range fn.Type.Params.List {
+ // get the parameter name
+ name := param.Names[0].Name
+ // get the parameter type
+ typ := exprToString(param.Type)
+ if strings.Contains(typ, ".") {
+ // replace with any
+ // we do not support or encourage passing external structs as parameters
+ typ = "any"
+ }
+ // add the parameter to the list of parameters
+ parameters = append(parameters, Parameter{
+ Name: name,
+ Type: toTsTypes(typ),
+ })
+ }
+ return parameters
+}
+
+// typeName is in format ssh.ClientConfig
+// it first fetches all fields from the struct and creates a new object
+// with that name and returns name of that object as type
+func (p *EntityParser) handleExternalStruct(typeName string) string {
+ baseType := typeName[strings.LastIndex(typeName, ".")+1:]
+ p.newObjects[typeName] = &Entity{
+ Name: baseType,
+ Type: "interface",
+ Description: baseType + " Object",
+ }
+ // @tarunKoyalwar: scrape and create new object
+ // pkg := pkgMap[strings.Split(tmp, ".")[0]]
+ // if pkg == nil {
+ // for k := range pkgMap {
+ // fmt.Println(k)
+ // }
+ // panic("package not found")
+ // }
+ // props, err := extractFieldsFromType(pkg, tmp[strings.LastIndex(tmp, ".")+1:])
+ // if err != nil {
+ // panic(err)
+ // }
+ // // newObject := Entity{
+ // // Name: tmp[strings.LastIndex(tmp, ".")+1:],
+ // // Type: "interface",
+ // // Object: Object{
+ // // Properties: props,
+ // // },
+ // // }
+ // fmt.Println(props)
+ return baseType
+}
+
+// extractStructTypes extracts all struct types from the AST
+func (p *EntityParser) extractStructTypes() {
+ for _, file := range p.syntax {
+ ast.Inspect(file, func(n ast.Node) bool {
+ // Check if the node is a type specification (which includes structs)
+ typeSpec, ok := n.(*ast.TypeSpec)
+ if ok {
+ // Check if the type specification is a struct type
+ _, ok := typeSpec.Type.(*ast.StructType)
+ if ok {
+ // Add the struct name to the list of struct names
+ p.structTypes[typeSpec.Name.Name] = Entity{
+ Name: typeSpec.Name.Name,
+ Description: typeSpec.Doc.Text(),
+ }
+ }
+ }
+ // Continue traversing the AST
+ return true
+ })
+ }
+
+}
+
+// extraGlobalConstant and vars
+func (p *EntityParser) extractVarsNConstants() {
+ p.vars = []Entity{}
+ for _, file := range p.syntax {
+ ast.Inspect(file, func(n ast.Node) bool {
+ // Check if the node is a type specification (which includes structs)
+ gen, ok := n.(*ast.GenDecl)
+ if !ok {
+ return true
+ }
+ for _, v := range gen.Specs {
+ switch spec := v.(type) {
+ case *ast.ValueSpec:
+ if !spec.Names[0].IsExported() {
+ continue
+ }
+ if spec.Values == nil || len(spec.Values) == 0 {
+ continue
+ }
+ // get comments or description
+ p.vars = append(p.vars, Entity{
+ Name: spec.Names[0].Name,
+ Type: "const",
+ Description: strings.TrimSpace(spec.Comment.Text()),
+ Value: spec.Values[0].(*ast.BasicLit).Value,
+ })
+ }
+ }
+ // Continue traversing the AST
+ return true
+ })
+ }
+}
+
+// loadImportedPackages loads all imported packages
+func (p *EntityParser) loadImportedPackages() error {
+ // get all import statements
+ // iterate over all imports
+ for _, file := range p.syntax {
+ for _, imp := range file.Imports {
+ // get the package path
+ path := imp.Path.Value
+ // remove the quotes from the path
+ path = path[1 : len(path)-1]
+ // load the package
+ pkg, err := loadPackage(path)
+ if err != nil {
+ return err
+ }
+ importName := path[strings.LastIndex(path, "/")+1:]
+ if imp.Name != nil {
+ importName = imp.Name.Name
+ } else {
+ if !strings.HasSuffix(imp.Path.Value, pkg.Types.Name()+`"`) {
+ importName = pkg.Types.Name()
+ }
+ }
+ // add the package to the map
+ if _, ok := p.imports[importName]; !ok {
+ p.imports[importName] = pkg
+ }
+ }
+ }
+ return nil
+}
+
+// Load the package containing the type definition
+// TODO: we don't support named imports yet
+func loadPackage(pkgPath string) (*packages.Package, error) {
+ cfg := &packages.Config{Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo}
+ pkgs, err := packages.Load(cfg, pkgPath)
+ if err != nil {
+ return nil, err
+ }
+ if len(pkgs) == 0 {
+ return nil, errors.New("no packages found")
+ }
+ return pkgs[0], nil
+}
+
+// exprToString converts an expression to a string
+func updateFuncWithConstructorSig(sig string, f Function) Function {
+ sig = strings.TrimSpace(sig)
+ f.Parameters = []Parameter{}
+ f.CanFail = true
+ f.ReturnStmt = ""
+ f.Returns = ""
+ if sig == "" {
+ return f
+ }
+ // example: constructor(public domain: string, public controller?: string)
+ // remove constructor( and )
+ sig = strings.TrimPrefix(sig, "constructor(")
+ sig = strings.TrimSuffix(sig, ")")
+ // split by comma
+ args := strings.Split(sig, ",")
+ for _, arg := range args {
+ arg = strings.TrimSpace(arg)
+ // check if it is optional
+ typeData := strings.Split(arg, ":")
+ if len(typeData) != 2 {
+ panic("invalid constructor signature")
+ }
+ f.Parameters = append(f.Parameters, Parameter{
+ Name: strings.TrimSpace(typeData[0]),
+ Type: strings.TrimSpace(typeData[1]),
+ })
+ }
+ return f
+}
diff --git a/pkg/js/devtools/tsgen/scrape.go b/pkg/js/devtools/tsgen/scrape.go
new file mode 100644
index 0000000000..960700f579
--- /dev/null
+++ b/pkg/js/devtools/tsgen/scrape.go
@@ -0,0 +1,192 @@
+package tsgen
+
+import (
+ "fmt"
+ "go/types"
+ "regexp"
+ "strings"
+
+ errorutil "github.com/projectdiscovery/utils/errors"
+)
+
+// scrape.go scrapes all information of exported type from different package
+
+func (p *EntityParser) scrapeAndCreate(typeName string) error {
+ if p.newObjects[typeName] == nil {
+ return nil
+ }
+ // get package name
+ pkgName := strings.Split(typeName, ".")[0]
+ baseTypeName := strings.Split(typeName, ".")[1]
+ // get package
+ pkg, ok := p.imports[pkgName]
+ if !ok {
+ return errorutil.New("package %v for type %v not found", pkgName, typeName)
+ }
+ // get type
+ obj := pkg.Types.Scope().Lookup(baseTypeName)
+ if obj == nil {
+ return errorutil.New("type %v not found in package %+v", typeName, pkg)
+ }
+ // Ensure the object is a type name
+ typeNameObj, ok := obj.(*types.TypeName)
+ if !ok {
+ return errorutil.New("%v is not a type name", typeName)
+ }
+ // Ensure the type is a named struct type
+ namedStruct, ok := typeNameObj.Type().Underlying().(*types.Struct)
+ if !ok {
+ return fmt.Errorf("%s is not a named struct type", typeName)
+ }
+ // fmt.Printf("got named struct %v\n", namedStruct)
+ // Iterate over the struct fields
+ d := &ExtObject{
+ builtIn: make(map[string]string),
+ nested: map[string]map[string]*ExtObject{},
+ }
+
+ // fmt.Printf("fields %v\n", namedStruct.NumFields())
+ for i := 0; i < namedStruct.NumFields(); i++ {
+ field := namedStruct.Field(i)
+ fieldName := field.Name()
+ if field.Exported() {
+ recursiveScrapeType(nil, fieldName, field.Type(), d)
+ }
+ }
+ entityMap := make(map[string]Entity)
+ // convert ExtObject to Entity
+ properties := ConvertExtObjectToEntities(d, entityMap)
+ entityMap[baseTypeName] = Entity{
+ Name: baseTypeName,
+ Type: "interface",
+ Description: fmt.Sprintf("%v Interface", baseTypeName),
+ Object: Interface{
+ Properties: properties,
+ },
+ }
+
+ for _, entity := range entityMap {
+ p.entities = append(p.entities, entity)
+ }
+
+ return nil
+}
+
+type ExtObject struct {
+ builtIn map[string]string
+ nested map[string]map[string]*ExtObject // Changed to map of field names to ExtObject
+}
+
+func recursiveScrapeType(parentType types.Type, fieldName string, fieldType types.Type, extObject *ExtObject) {
+ if named, ok := fieldType.(*types.Named); ok && !named.Obj().Exported() {
+ // fmt.Printf("type %v is not exported\n", named.Obj().Name())
+ return
+ }
+
+ if fieldType.String() == "time.Time" {
+ extObject.builtIn[fieldName] = "Date"
+ return
+ }
+
+ switch t := fieldType.Underlying().(type) {
+ case *types.Pointer:
+ // fmt.Printf("type %v is a pointer\n", fieldType)
+ recursiveScrapeType(nil, fieldName, t.Elem(), extObject)
+ case *types.Signature:
+ // fmt.Printf("type %v is a callback or interface\n", fieldType)
+ case *types.Basic:
+ // Check for basic types (built-in types)
+ if parentType != nil {
+ switch p := parentType.Underlying().(type) {
+ case *types.Slice:
+ extObject.builtIn[fieldName] = "[]" + fieldType.String()
+ case *types.Array:
+ extObject.builtIn[fieldName] = fmt.Sprintf("[%v]", p.Len()) + fieldType.String()
+ }
+ } else {
+ extObject.builtIn[fieldName] = fieldType.String()
+ }
+ case *types.Struct:
+ // Check for struct types
+ if extObject.nested[fieldName] == nil {
+ // @tarunKoyalwar: it currently does not supported struct arrays
+ extObject.nested[fieldName] = make(map[string]*ExtObject)
+ }
+ nestedExtObject := &ExtObject{
+ builtIn: make(map[string]string),
+ nested: map[string]map[string]*ExtObject{},
+ }
+ extObject.nested[fieldName][fieldType.String()] = nestedExtObject
+ for i := 0; i < t.NumFields(); i++ {
+ field := t.Field(i)
+ if field.Exported() {
+ recursiveScrapeType(nil, field.Name(), field.Type(), nestedExtObject)
+ }
+ }
+ case *types.Array:
+ // fmt.Printf("type %v is an array\n", fieldType)
+ // get array type
+ recursiveScrapeType(t, fieldName, t.Elem(), extObject)
+ case *types.Slice:
+ // fmt.Printf("type %v is a slice\n", fieldType)
+ // get slice type
+ recursiveScrapeType(t, fieldName, t.Elem(), extObject)
+ default:
+ // fmt.Printf("type %v is not a builtIn or struct\n", fieldType)
+ }
+}
+
+var re = regexp.MustCompile(`\[[0-9]+\].*`)
+
+// ConvertExtObjectToEntities recursively converts an ExtObject to a list of Entity objects
+func ConvertExtObjectToEntities(extObj *ExtObject, nestedTypes map[string]Entity) []Property {
+ var properties []Property
+
+ // Iterate over the built-in types
+ for fieldName, fieldType := range extObj.builtIn {
+ var description string
+ if re.MatchString(fieldType) {
+ // if it is a fixed size array add len in description
+ description = fmt.Sprintf("fixed size array of length: %v", fieldType[:strings.Index(fieldType, "]")+1])
+ // remove length from type
+ fieldType = "[]" + fieldType[strings.Index(fieldType, "]")+1:]
+ }
+ if strings.Contains(fieldType, "time.Duration") {
+ description = "time in nanoseconds"
+ }
+ px := Property{
+ Name: fieldName,
+ Type: toTsTypes(fieldType),
+ Description: description,
+ }
+
+ if strings.HasPrefix(px.Type, "[") {
+ px.Type = fieldType[strings.Index(px.Type, "]")+1:] + "[]"
+ }
+ properties = append(properties, px)
+ }
+
+ // Iterate over the nested types
+ for fieldName, nestedExtObjects := range extObj.nested {
+ for origType, nestedExtObject := range nestedExtObjects {
+ // fix:me this nestedExtObject always has only one element
+ got := ConvertExtObjectToEntities(nestedExtObject, nestedTypes)
+ baseTypename := origType[strings.LastIndex(origType, ".")+1:]
+ // create new nestedType
+ nestedTypes[baseTypename] = Entity{
+ Name: baseTypename,
+ Description: fmt.Sprintf("%v Interface", baseTypename),
+ Type: "interface",
+ Object: Interface{
+ Properties: got,
+ },
+ }
+ // assign current field type to nested type
+ properties = append(properties, Property{
+ Name: fieldName,
+ Type: baseTypename,
+ })
+ }
+ }
+ return properties
+}
diff --git a/pkg/js/devtools/tsgen/types.go b/pkg/js/devtools/tsgen/types.go
new file mode 100644
index 0000000000..d7e67e6c8a
--- /dev/null
+++ b/pkg/js/devtools/tsgen/types.go
@@ -0,0 +1,60 @@
+package tsgen
+
+// Define a struct to hold information about your TypeScript entities
+type Entity struct {
+ Name string
+ Value string
+ Type string // "class", "function", or "object" or "interface" or "const"
+ Description string
+ Example string // this will be part of description with @example jsdoc tag
+ Class Class // if Type == "class"
+ Function Function // if Type == "function"
+ Object Interface // if Type == "object"
+ IsConstructor bool // true if this is a constructor function
+}
+
+// Class represents a TypeScript class data structure
+type Class struct {
+ Properties []Property
+ Methods []Method
+ Constructor Function
+}
+
+// Function represents a TypeScript function data structure
+// If CanFail is true, the function returns a Result type
+// So modify the function signature to return a Result type in this case
+type Function struct {
+ Parameters []Parameter
+ Returns string
+ CanFail bool
+ ReturnStmt string
+}
+
+type Interface struct {
+ Properties []Property
+}
+
+// Method represents a TypeScript method data structure
+// If CanFail is true, the method returns a Result type
+// So modify the method signature to return a Result type in this case
+type Method struct {
+ Name string
+ Description string
+ Parameters []Parameter
+ Returns string
+ CanFail bool
+ ReturnStmt string
+}
+
+// Property represent class or object property
+type Property struct {
+ Name string
+ Type string
+ Description string
+}
+
+// Parameter represents function or method parameter
+type Parameter struct {
+ Name string
+ Type string
+}
diff --git a/pkg/js/generated/go/libbytes/bytes.go b/pkg/js/generated/go/libbytes/bytes.go
index ec521be348..c2955acf42 100644
--- a/pkg/js/generated/go/libbytes/bytes.go
+++ b/pkg/js/generated/go/libbytes/bytes.go
@@ -19,10 +19,8 @@ func init() {
// Var and consts
- // Types (value type)
- "Buffer": func() lib_bytes.Buffer { return lib_bytes.Buffer{} },
-
- // Types (pointer type)
+ // Objects / Classes
+ "Buffer": lib_bytes.NewBuffer,
},
).Register()
}
diff --git a/pkg/js/generated/go/libfs/fs.go b/pkg/js/generated/go/libfs/fs.go
index 73056a89b2..bc3e509931 100644
--- a/pkg/js/generated/go/libfs/fs.go
+++ b/pkg/js/generated/go/libfs/fs.go
@@ -22,9 +22,8 @@ func init() {
// Var and consts
- // Types (value type)
+ // Objects / Classes
- // Types (pointer type)
},
).Register()
}
diff --git a/pkg/js/generated/go/libgoconsole/goconsole.go b/pkg/js/generated/go/libgoconsole/goconsole.go
index 7b057de3cb..c8056d5051 100644
--- a/pkg/js/generated/go/libgoconsole/goconsole.go
+++ b/pkg/js/generated/go/libgoconsole/goconsole.go
@@ -19,10 +19,8 @@ func init() {
// Var and consts
- // Types (value type)
- "GoConsolePrinter": func() lib_goconsole.GoConsolePrinter { return lib_goconsole.GoConsolePrinter{} },
-
- // Types (pointer type)
+ // Objects / Classes
+ "GoConsolePrinter": gojs.GetClassConstructor[lib_goconsole.GoConsolePrinter](&lib_goconsole.GoConsolePrinter{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libikev2/ikev2.go b/pkg/js/generated/go/libikev2/ikev2.go
index 639f14aa53..9d7e588244 100644
--- a/pkg/js/generated/go/libikev2/ikev2.go
+++ b/pkg/js/generated/go/libikev2/ikev2.go
@@ -26,15 +26,10 @@ func init() {
"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{} },
+ // Objects / Classes
+ "IKEMessage": gojs.GetClassConstructor[lib_ikev2.IKEMessage](&lib_ikev2.IKEMessage{}),
+ "IKENonce": gojs.GetClassConstructor[lib_ikev2.IKENonce](&lib_ikev2.IKENonce{}),
+ "IKENotification": gojs.GetClassConstructor[lib_ikev2.IKENotification](&lib_ikev2.IKENotification{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libkerberos/kerberos.go b/pkg/js/generated/go/libkerberos/kerberos.go
index 2b1fe49130..db367ef562 100644
--- a/pkg/js/generated/go/libkerberos/kerberos.go
+++ b/pkg/js/generated/go/libkerberos/kerberos.go
@@ -15,16 +15,20 @@ func init() {
module.Set(
gojs.Objects{
// Functions
+ "ASRepToHashcat": lib_kerberos.ASRepToHashcat,
+ "CheckKrbError": lib_kerberos.CheckKrbError,
+ "NewKerberosClient": lib_kerberos.NewKerberosClient,
+ "NewKerberosClientFromString": lib_kerberos.NewKerberosClientFromString,
+ "SendToKDC": lib_kerberos.SendToKDC,
+ "TGStoHashcat": lib_kerberos.TGStoHashcat,
// 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{} },
+ // Objects / Classes
+ "Client": lib_kerberos.NewKerberosClient,
+ "Config": gojs.GetClassConstructor[lib_kerberos.Config](&lib_kerberos.Config{}),
+ "EnumerateUserResponse": gojs.GetClassConstructor[lib_kerberos.EnumerateUserResponse](&lib_kerberos.EnumerateUserResponse{}),
+ "TGS": gojs.GetClassConstructor[lib_kerberos.TGS](&lib_kerberos.TGS{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libldap/ldap.go b/pkg/js/generated/go/libldap/ldap.go
index 1b89f4be99..8d0c2702e4 100644
--- a/pkg/js/generated/go/libldap/ldap.go
+++ b/pkg/js/generated/go/libldap/ldap.go
@@ -15,16 +15,48 @@ func init() {
module.Set(
gojs.Objects{
// Functions
+ "DecodeADTimestamp": lib_ldap.DecodeADTimestamp,
+ "DecodeSID": lib_ldap.DecodeSID,
+ "DecodeZuluTimestamp": lib_ldap.DecodeZuluTimestamp,
+ "JoinFilters": lib_ldap.JoinFilters,
+ "NegativeFilter": lib_ldap.NegativeFilter,
+ "NewClient": lib_ldap.NewClient,
// Var and consts
+ "FilterAccountDisabled": lib_ldap.FilterAccountDisabled,
+ "FilterAccountEnabled": lib_ldap.FilterAccountEnabled,
+ "FilterCanSendEncryptedPassword": lib_ldap.FilterCanSendEncryptedPassword,
+ "FilterDontExpirePassword": lib_ldap.FilterDontExpirePassword,
+ "FilterDontRequirePreauth": lib_ldap.FilterDontRequirePreauth,
+ "FilterHasServicePrincipalName": lib_ldap.FilterHasServicePrincipalName,
+ "FilterHomedirRequired": lib_ldap.FilterHomedirRequired,
+ "FilterInterdomainTrustAccount": lib_ldap.FilterInterdomainTrustAccount,
+ "FilterIsAdmin": lib_ldap.FilterIsAdmin,
+ "FilterIsComputer": lib_ldap.FilterIsComputer,
+ "FilterIsDuplicateAccount": lib_ldap.FilterIsDuplicateAccount,
+ "FilterIsGroup": lib_ldap.FilterIsGroup,
+ "FilterIsNormalAccount": lib_ldap.FilterIsNormalAccount,
+ "FilterIsPerson": lib_ldap.FilterIsPerson,
+ "FilterLockout": lib_ldap.FilterLockout,
+ "FilterLogonScript": lib_ldap.FilterLogonScript,
+ "FilterMnsLogonAccount": lib_ldap.FilterMnsLogonAccount,
+ "FilterNotDelegated": lib_ldap.FilterNotDelegated,
+ "FilterPartialSecretsAccount": lib_ldap.FilterPartialSecretsAccount,
+ "FilterPasswordCantChange": lib_ldap.FilterPasswordCantChange,
+ "FilterPasswordExpired": lib_ldap.FilterPasswordExpired,
+ "FilterPasswordNotRequired": lib_ldap.FilterPasswordNotRequired,
+ "FilterServerTrustAccount": lib_ldap.FilterServerTrustAccount,
+ "FilterSmartCardRequired": lib_ldap.FilterSmartCardRequired,
+ "FilterTrustedForDelegation": lib_ldap.FilterTrustedForDelegation,
+ "FilterTrustedToAuthForDelegation": lib_ldap.FilterTrustedToAuthForDelegation,
+ "FilterUseDesKeyOnly": lib_ldap.FilterUseDesKeyOnly,
+ "FilterWorkstationTrustAccount": lib_ldap.FilterWorkstationTrustAccount,
- // 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{} },
+ // Objects / Classes
+ "ADObject": gojs.GetClassConstructor[lib_ldap.ADObject](&lib_ldap.ADObject{}),
+ "Client": lib_ldap.NewClient,
+ "Config": gojs.GetClassConstructor[lib_ldap.Config](&lib_ldap.Config{}),
+ "Metadata": gojs.GetClassConstructor[lib_ldap.Metadata](&lib_ldap.Metadata{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libmssql/mssql.go b/pkg/js/generated/go/libmssql/mssql.go
index ba2a2c57bf..48edb8352e 100644
--- a/pkg/js/generated/go/libmssql/mssql.go
+++ b/pkg/js/generated/go/libmssql/mssql.go
@@ -18,11 +18,8 @@ func init() {
// 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{} },
+ // Objects / Classes
+ "MSSQLClient": gojs.GetClassConstructor[lib_mssql.MSSQLClient](&lib_mssql.MSSQLClient{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libmysql/mysql.go b/pkg/js/generated/go/libmysql/mysql.go
index 7c8c760022..1ec1817011 100644
--- a/pkg/js/generated/go/libmysql/mysql.go
+++ b/pkg/js/generated/go/libmysql/mysql.go
@@ -15,14 +15,14 @@ func init() {
module.Set(
gojs.Objects{
// Functions
+ "BuildDSN": lib_mysql.BuildDSN,
// 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{} },
+ // Objects / Classes
+ "MySQLClient": gojs.GetClassConstructor[lib_mysql.MySQLClient](&lib_mysql.MySQLClient{}),
+ "MySQLInfo": gojs.GetClassConstructor[lib_mysql.MySQLInfo](&lib_mysql.MySQLInfo{}),
+ "MySQLOptions": gojs.GetClassConstructor[lib_mysql.MySQLOptions](&lib_mysql.MySQLOptions{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libnet/net.go b/pkg/js/generated/go/libnet/net.go
index a41cb99f4d..031bba2ba7 100644
--- a/pkg/js/generated/go/libnet/net.go
+++ b/pkg/js/generated/go/libnet/net.go
@@ -20,11 +20,8 @@ func init() {
// 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{} },
+ // Objects / Classes
+ "NetConn": gojs.GetClassConstructor[lib_net.NetConn](&lib_net.NetConn{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/liboracle/oracle.go b/pkg/js/generated/go/liboracle/oracle.go
index 5d84d90235..53c8dee1c5 100644
--- a/pkg/js/generated/go/liboracle/oracle.go
+++ b/pkg/js/generated/go/liboracle/oracle.go
@@ -15,16 +15,12 @@ func init() {
module.Set(
gojs.Objects{
// Functions
+ "IsOracle": lib_oracle.IsOracle,
// 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{} },
+ // Objects / Classes
+ "IsOracleResponse": gojs.GetClassConstructor[lib_oracle.IsOracleResponse](&lib_oracle.IsOracleResponse{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libpop3/pop3.go b/pkg/js/generated/go/libpop3/pop3.go
index d1e7a865fb..c84436e2fb 100644
--- a/pkg/js/generated/go/libpop3/pop3.go
+++ b/pkg/js/generated/go/libpop3/pop3.go
@@ -15,16 +15,12 @@ func init() {
module.Set(
gojs.Objects{
// Functions
+ "IsPOP3": lib_pop3.IsPOP3,
// 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{} },
+ // Objects / Classes
+ "IsPOP3Response": gojs.GetClassConstructor[lib_pop3.IsPOP3Response](&lib_pop3.IsPOP3Response{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libpostgres/postgres.go b/pkg/js/generated/go/libpostgres/postgres.go
index 0af652397e..0230c75b86 100644
--- a/pkg/js/generated/go/libpostgres/postgres.go
+++ b/pkg/js/generated/go/libpostgres/postgres.go
@@ -18,11 +18,8 @@ func init() {
// 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{} },
+ // Objects / Classes
+ "PGClient": gojs.GetClassConstructor[lib_postgres.PGClient](&lib_postgres.PGClient{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/librdp/rdp.go b/pkg/js/generated/go/librdp/rdp.go
index c975fe7783..f3129ef21a 100644
--- a/pkg/js/generated/go/librdp/rdp.go
+++ b/pkg/js/generated/go/librdp/rdp.go
@@ -15,18 +15,14 @@ func init() {
module.Set(
gojs.Objects{
// Functions
+ "CheckRDPAuth": lib_rdp.CheckRDPAuth,
+ "IsRDP": lib_rdp.IsRDP,
// 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{} },
+ // Objects / Classes
+ "CheckRDPAuthResponse": gojs.GetClassConstructor[lib_rdp.CheckRDPAuthResponse](&lib_rdp.CheckRDPAuthResponse{}),
+ "IsRDPResponse": gojs.GetClassConstructor[lib_rdp.IsRDPResponse](&lib_rdp.IsRDPResponse{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libredis/redis.go b/pkg/js/generated/go/libredis/redis.go
index 06aa37da79..a633afd846 100644
--- a/pkg/js/generated/go/libredis/redis.go
+++ b/pkg/js/generated/go/libredis/redis.go
@@ -23,9 +23,8 @@ func init() {
// Var and consts
- // Types (value type)
+ // Objects / Classes
- // Types (pointer type)
},
).Register()
}
diff --git a/pkg/js/generated/go/librsync/rsync.go b/pkg/js/generated/go/librsync/rsync.go
index 51baab04bd..a8e925d8de 100644
--- a/pkg/js/generated/go/librsync/rsync.go
+++ b/pkg/js/generated/go/librsync/rsync.go
@@ -15,16 +15,12 @@ func init() {
module.Set(
gojs.Objects{
// Functions
+ "IsRsync": lib_rsync.IsRsync,
// 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{} },
+ // Objects / Classes
+ "IsRsyncResponse": gojs.GetClassConstructor[lib_rsync.IsRsyncResponse](&lib_rsync.IsRsyncResponse{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libsmb/smb.go b/pkg/js/generated/go/libsmb/smb.go
index 5181881a84..2afe53c687 100644
--- a/pkg/js/generated/go/libsmb/smb.go
+++ b/pkg/js/generated/go/libsmb/smb.go
@@ -18,11 +18,8 @@ func init() {
// 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{} },
+ // Objects / Classes
+ "SMBClient": gojs.GetClassConstructor[lib_smb.SMBClient](&lib_smb.SMBClient{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libsmtp/smtp.go b/pkg/js/generated/go/libsmtp/smtp.go
index 75b83bd3ea..e27f55ac77 100644
--- a/pkg/js/generated/go/libsmtp/smtp.go
+++ b/pkg/js/generated/go/libsmtp/smtp.go
@@ -15,18 +15,14 @@ func init() {
module.Set(
gojs.Objects{
// Functions
+ "NewSMTPClient": lib_smtp.NewSMTPClient,
// Var and consts
- // Types (value type)
- "IsSMTPResponse": func() lib_smtp.IsSMTPResponse { return lib_smtp.IsSMTPResponse{} },
- "SMTPClient": func() lib_smtp.SMTPClient { return lib_smtp.SMTPClient{} },
- "SMTPMessage": func() lib_smtp.SMTPMessage { return lib_smtp.SMTPMessage{} },
-
- // Types (pointer type)
- "NewIsSMTPResponse": func() *lib_smtp.IsSMTPResponse { return &lib_smtp.IsSMTPResponse{} },
- "NewSMTPClient": func() *lib_smtp.SMTPClient { return &lib_smtp.SMTPClient{} },
- "NewSMTPMessage": func() *lib_smtp.SMTPMessage { return &lib_smtp.SMTPMessage{} },
+ // Objects / Classes
+ "Client": lib_smtp.NewSMTPClient,
+ "SMTPMessage": gojs.GetClassConstructor[lib_smtp.SMTPMessage](&lib_smtp.SMTPMessage{}),
+ "SMTPResponse": gojs.GetClassConstructor[lib_smtp.SMTPResponse](&lib_smtp.SMTPResponse{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libssh/ssh.go b/pkg/js/generated/go/libssh/ssh.go
index 9341f7df9e..6a36f51eb9 100644
--- a/pkg/js/generated/go/libssh/ssh.go
+++ b/pkg/js/generated/go/libssh/ssh.go
@@ -18,11 +18,8 @@ func init() {
// 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{} },
+ // Objects / Classes
+ "SSHClient": gojs.GetClassConstructor[lib_ssh.SSHClient](&lib_ssh.SSHClient{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libstructs/structs.go b/pkg/js/generated/go/libstructs/structs.go
index b11fb5fb53..e17e629dd4 100644
--- a/pkg/js/generated/go/libstructs/structs.go
+++ b/pkg/js/generated/go/libstructs/structs.go
@@ -21,9 +21,8 @@ func init() {
// Var and consts
- // Types (value type)
+ // Objects / Classes
- // Types (pointer type)
},
).Register()
}
diff --git a/pkg/js/generated/go/libtelnet/telnet.go b/pkg/js/generated/go/libtelnet/telnet.go
index cf43761d29..82a08c253d 100644
--- a/pkg/js/generated/go/libtelnet/telnet.go
+++ b/pkg/js/generated/go/libtelnet/telnet.go
@@ -15,16 +15,12 @@ func init() {
module.Set(
gojs.Objects{
// Functions
+ "IsTelnet": lib_telnet.IsTelnet,
// 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{} },
+ // Objects / Classes
+ "IsTelnetResponse": gojs.GetClassConstructor[lib_telnet.IsTelnetResponse](&lib_telnet.IsTelnetResponse{}),
},
).Register()
}
diff --git a/pkg/js/generated/go/libvnc/vnc.go b/pkg/js/generated/go/libvnc/vnc.go
index cb0e2fd5d1..affc3c9331 100644
--- a/pkg/js/generated/go/libvnc/vnc.go
+++ b/pkg/js/generated/go/libvnc/vnc.go
@@ -15,16 +15,12 @@ func init() {
module.Set(
gojs.Objects{
// Functions
+ "IsVNC": lib_vnc.IsVNC,
// 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{} },
+ // Objects / Classes
+ "IsVNCResponse": gojs.GetClassConstructor[lib_vnc.IsVNCResponse](&lib_vnc.IsVNCResponse{}),
},
).Register()
}
diff --git a/pkg/js/generated/js/global.js b/pkg/js/generated/js/global.js
deleted file mode 100644
index bcbdfd7a5c..0000000000
--- a/pkg/js/generated/js/global.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * @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/pkg/js/generated/js/libbytes/bytes.js b/pkg/js/generated/js/libbytes/bytes.js
deleted file mode 100644
index bd0b1f4f8e..0000000000
--- a/pkg/js/generated/js/libbytes/bytes.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/**@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/pkg/js/generated/js/libfs/fs.js b/pkg/js/generated/js/libfs/fs.js
deleted file mode 100644
index 026c07ab76..0000000000
--- a/pkg/js/generated/js/libfs/fs.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/** @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/pkg/js/generated/js/libgoconsole/goconsole.js b/pkg/js/generated/js/libgoconsole/goconsole.js
deleted file mode 100644
index deeddd97ed..0000000000
--- a/pkg/js/generated/js/libgoconsole/goconsole.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/** @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/pkg/js/generated/js/libikev2/ikev2.js b/pkg/js/generated/js/libikev2/ikev2.js
deleted file mode 100644
index 46fda80126..0000000000
--- a/pkg/js/generated/js/libikev2/ikev2.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/** @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/pkg/js/generated/js/libkerberos/kerberos.js b/pkg/js/generated/js/libkerberos/kerberos.js
deleted file mode 100644
index 4f73770501..0000000000
--- a/pkg/js/generated/js/libkerberos/kerberos.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/** @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/pkg/js/generated/js/libldap/ldap.js b/pkg/js/generated/js/libldap/ldap.js
deleted file mode 100644
index 9297b9232c..0000000000
--- a/pkg/js/generated/js/libldap/ldap.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/** @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/pkg/js/generated/js/libmssql/mssql.js b/pkg/js/generated/js/libmssql/mssql.js
deleted file mode 100644
index 81f5a32d9a..0000000000
--- a/pkg/js/generated/js/libmssql/mssql.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/** @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/pkg/js/generated/js/libmysql/mysql.js b/pkg/js/generated/js/libmysql/mysql.js
deleted file mode 100644
index afa5a22e79..0000000000
--- a/pkg/js/generated/js/libmysql/mysql.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/** @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/pkg/js/generated/js/libnet/net.js b/pkg/js/generated/js/libnet/net.js
deleted file mode 100644
index 40e830b5d5..0000000000
--- a/pkg/js/generated/js/libnet/net.js
+++ /dev/null
@@ -1,156 +0,0 @@
-/**@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 all available data.
- * @param {number} [N=0] - 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 all available data.
- * @param {number} [N=0] - 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 all available data.
- * @param {number} [N=0] - 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/pkg/js/generated/js/liboracle/oracle.js b/pkg/js/generated/js/liboracle/oracle.js
deleted file mode 100644
index 2341342d7a..0000000000
--- a/pkg/js/generated/js/liboracle/oracle.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/** @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/pkg/js/generated/js/libpop3/pop3.js b/pkg/js/generated/js/libpop3/pop3.js
deleted file mode 100644
index 595efa3fcb..0000000000
--- a/pkg/js/generated/js/libpop3/pop3.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/** @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/pkg/js/generated/js/libpostgres/postgres.js b/pkg/js/generated/js/libpostgres/postgres.js
deleted file mode 100644
index 71588f6b97..0000000000
--- a/pkg/js/generated/js/libpostgres/postgres.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/** @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/pkg/js/generated/js/librdp/rdp.js b/pkg/js/generated/js/librdp/rdp.js
deleted file mode 100644
index 456837dc37..0000000000
--- a/pkg/js/generated/js/librdp/rdp.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/**@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/pkg/js/generated/js/libredis/redis.js b/pkg/js/generated/js/libredis/redis.js
deleted file mode 100644
index bbff60b437..0000000000
--- a/pkg/js/generated/js/libredis/redis.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/** @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/pkg/js/generated/js/librsync/rsync.js b/pkg/js/generated/js/librsync/rsync.js
deleted file mode 100644
index c5d1271da2..0000000000
--- a/pkg/js/generated/js/librsync/rsync.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/** @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/pkg/js/generated/js/libsmb/smb.js b/pkg/js/generated/js/libsmb/smb.js
deleted file mode 100644
index a14333a591..0000000000
--- a/pkg/js/generated/js/libsmb/smb.js
+++ /dev/null
@@ -1,90 +0,0 @@
-/** @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/pkg/js/generated/js/libsmtp/smtp.js b/pkg/js/generated/js/libsmtp/smtp.js
deleted file mode 100644
index 775c028091..0000000000
--- a/pkg/js/generated/js/libsmtp/smtp.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/** @module smtp */
-
-/**
- * @class
- * @classdesc SMTPClient is a minimal SMTP client for nuclei scripts.
- */
-class SMTPClient {
- /**
- @method
- @description IsOpenRelay checks if a host is an open relay
- @param {string} host - The host to check.
- @param {number} port - The port to check.
- @param {string} msg - The message to send.
- @returns {boolean} - Whether the host is an open relay or not.
- @throws {error} - The error encountered during the check.
- @example
- let m = require('nuclei/smtp');
- let c = m.SMTPClient();
- let isOpenRelay = c.IsOpenRelay('localhost', 25, 'test message');
- */
- IsOpenRelay(host, port, msg) {
- // implemented in go
- };
-
- /**
- @method
- @description IsSMTP checks if a host is running a SMTP server.
- @param {string} host - The host to check.
- @param {number} port - The port to check.
- @returns {IsSMTPResponse} - The response from the SMTP server.
- @throws {error} - The error encountered during the check.
- @example
- let m = require('nuclei/smtp');
- let c = m.SMTPClient();
- let isSMTP = c.IsSMTP('localhost', 25);
- */
- IsSMTP(host, port) {
- // implemented in go
- };
-
- /**
- @method
- @description SendMail sends an email using the SMTP protocol.
- @param {string} host - The host to send the email to.
- @param {number} port - The port to send the email to.
- @param {string} msg - The message to send.
- @returns {boolean} - Whether the email was sent successfully or not.
- @throws {error} - The error encountered during the email sending.
- @example
- let m = require('nuclei/smtp');
- let c = m.SMTPClient();
- let isSent = c.SendMail('localhost', 25, 'test message');
- */
- SendMail(host, port, msg) {
- // implemented in go
- };
-};
-
-/**
- * @class
- * @classdesc SMTPMessage is a simple smtp message builder
- */
-class SMTPMessage {
- /**
- @method
- @description Auth when called authenticates using username and password before sending the message
- @param {string} username - The username for authentication.
- @param {string} password - The password for authentication.
- @returns {SMTPMessage} - The SMTPMessage object after authentication.
- @example
- let m = require('nuclei/smtp');
- let msg = m.SMTPMessage();
- msg = msg.Auth('username', 'password');
- */
- Auth(username, password) {
- // implemented in go
- };
-
- /**
- @method
- @description Body adds the message body to the message
- @param {string} msg - The message body to add.
- @returns {SMTPMessage} - The SMTPMessage object after adding the body.
- @example
- let m = require('nuclei/smtp');
- let msg = m.SMTPMessage();
- msg = msg.Body('This is a test message');
- */
- Body(msg) {
- // implemented in go
- };
-
- /**
- @method
- @description From adds the from field to the message
- @param {string} email - The email to add to the from field.
- @returns {SMTPMessage} - The SMTPMessage object after adding the from field.
- @example
- let m = require('nuclei/smtp');
- let msg = m.SMTPMessage();
- msg = msg.From('test@example.com');
- */
- From(email) {
- // implemented in go
- };
-
- /**
- @method
- @description String returns the string representation of the message
- @returns {string} - The string representation of the message.
- @example
- let m = require('nuclei/smtp');
- let msg = m.SMTPMessage();
- let str = msg.String();
- */
- String() {
- // implemented in go
- };
-
- /**
- @method
- @description Subject adds the subject field to the message
- @param {string} sub - The subject to add.
- @returns {SMTPMessage} - The SMTPMessage object after adding the subject.
- @example
- let m = require('nuclei/smtp');
- let msg = m.SMTPMessage();
- msg = msg.Subject('Test Subject');
- */
- Subject(sub) {
- // implemented in go
- };
-
- /**
- @method
- @description To adds the to field to the message
- @param {string} email - The email to add to the to field.
- @returns {SMTPMessage} - The SMTPMessage object after adding the to field.
- @example
- let m = require('nuclei/smtp');
- let msg = m.SMTPMessage();
- msg = msg.To('test@example.com');
- */
- To(email) {
- // implemented in go
- };
-};
-
-module.exports = {
- SMTPClient: SMTPClient,
- SMTPMessage: SMTPMessage,
-};
\ No newline at end of file
diff --git a/pkg/js/generated/js/libssh/ssh.js b/pkg/js/generated/js/libssh/ssh.js
deleted file mode 100644
index 984604dd9d..0000000000
--- a/pkg/js/generated/js/libssh/ssh.js
+++ /dev/null
@@ -1,112 +0,0 @@
-/**@module ssh */
-
-/**
- * @typedef {object} HandshakeLog
- * @description HandshakeLog is a struct that contains information about the ssh connection.
- */
-const HandshakeLog = {};
-
-/**
- * @class
- * @classdesc SSHClient is a client for SSH servers. Internally client uses github.com/zmap/zgrab2/lib/ssh driver.
- */
-class SSHClient {
- /**
- @method
- @description Close closes the SSH connection and destroys the client
- @returns {boolean} - The success state of the operation.
- @throws {error} - The error encountered during the operation.
- @example
- let m = require('nuclei/ssh');
- let c = m.SSHClient();
- let state = c.Connect('localhost', 22, 'user', 'password');
- let result = c.Close();
- */
- Close() {
- // implemented in go
- };
-
- /**
- @method
- @description Connect tries to connect to provided host and port with provided username and password with ssh.
- @param {string} host - The host to connect to.
- @param {number} port - The port to connect to.
- @param {string} username - The username for the connection.
- @param {string} password - The password for the connection.
- @returns {boolean} - The success state of the operation.
- @throws {error} - The error encountered during the operation.
- @example
- let m = require('nuclei/ssh');
- let c = m.SSHClient();
- let result = 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 with provided host and port
- @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 the operation.
- @example
- let m = require('nuclei/ssh');
- let c = m.SSHClient();
- let result = 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.
- @param {string} host - The host to connect to.
- @param {number} port - The port to connect to.
- @param {string} username - The username for the connection.
- @param {string} key - The private key for the connection.
- @returns {boolean} - The success state of the operation.
- @throws {error} - The error encountered during the operation.
- @example
- let m = require('nuclei/ssh');
- let c = m.SSHClient();
- let result = c.ConnectWithKey('localhost', 22, 'user', 'key');
- */
- ConnectWithKey(host, port, username, key) {
- // implemented in go
- };
-
- /**
- @method
- @description Run tries to open a new SSH session, then tries to execute the provided command in said session
- @param {string} cmd - The command to execute.
- @returns {string} - The output of the command.
- @throws {error} - The error encountered during the operation.
- @example
- let m = require('nuclei/ssh');
- let c = m.SSHClient();
- let result = c.Run('ls');
- */
- Run(cmd) {
- // implemented in go
- };
-
- /**
- @method
- @description SetTimeout sets the timeout for the SSH connection in seconds
- @param {number} sec - The number of seconds for the timeout.
- @example
- let m = require('nuclei/ssh');
- let c = m.SSHClient();
- c.SetTimeout(30);
- */
- SetTimeout(sec) {
- // implemented in go
- };
-};
-
-module.exports = {
- SSHClient: SSHClient,
-};
\ No newline at end of file
diff --git a/pkg/js/generated/js/libstructs/structs.js b/pkg/js/generated/js/libstructs/structs.js
deleted file mode 100644
index 0ec94de25a..0000000000
--- a/pkg/js/generated/js/libstructs/structs.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/**@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/pkg/js/generated/js/libtelnet/telnet.js b/pkg/js/generated/js/libtelnet/telnet.js
deleted file mode 100644
index 4217eb0eaa..0000000000
--- a/pkg/js/generated/js/libtelnet/telnet.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/** @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/pkg/js/generated/js/libvnc/vnc.js b/pkg/js/generated/js/libvnc/vnc.js
deleted file mode 100644
index 4afd1eeb39..0000000000
--- a/pkg/js/generated/js/libvnc/vnc.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/** @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/pkg/js/generated/ts/bytes.ts b/pkg/js/generated/ts/bytes.ts
new file mode 100755
index 0000000000..b0ed541687
--- /dev/null
+++ b/pkg/js/generated/ts/bytes.ts
@@ -0,0 +1,141 @@
+
+
+/**
+ * Buffer is a bytes/Uint8Array type in javascript
+ * @example
+ * ```javascript
+ * const bytes = require('nuclei/bytes');
+ * const bytes = new bytes.Buffer();
+ * ```
+ * @example
+ * ```javascript
+ * const bytes = require('nuclei/bytes');
+ * // optionally it can accept existing byte/Uint8Array as input
+ * const bytes = new bytes.Buffer([1, 2, 3]);
+ * ```
+ */
+export class Buffer {
+
+
+ // Constructor of Buffer
+ constructor() {}
+ /**
+ * Write appends the given data to the buffer.
+ * @example
+ * ```javascript
+ * const bytes = require('nuclei/bytes');
+ * const buffer = new bytes.Buffer();
+ * buffer.Write([1, 2, 3]);
+ * ```
+ */
+ public Write(data: Uint8Array): Buffer {
+ return this;
+ }
+
+
+ /**
+ * WriteString appends the given string data to the buffer.
+ * @example
+ * ```javascript
+ * const bytes = require('nuclei/bytes');
+ * const buffer = new bytes.Buffer();
+ * buffer.WriteString('hello');
+ * ```
+ */
+ public WriteString(data: string): Buffer {
+ return this;
+ }
+
+
+ /**
+ * Bytes returns the byte representation of the buffer.
+ * @example
+ * ```javascript
+ * const bytes = require('nuclei/bytes');
+ * const buffer = new bytes.Buffer();
+ * buffer.WriteString('hello');
+ * log(buffer.Bytes());
+ * ```
+ */
+ public Bytes(): Uint8Array {
+ return new Uint8Array(8);
+ }
+
+
+ /**
+ * String returns the string representation of the buffer.
+ * @example
+ * ```javascript
+ * const bytes = require('nuclei/bytes');
+ * const buffer = new bytes.Buffer();
+ * buffer.WriteString('hello');
+ * log(buffer.String());
+ * ```
+ */
+ public String(): string {
+ return "";
+ }
+
+
+ /**
+ * Len returns the length of the buffer.
+ * @example
+ * ```javascript
+ * const bytes = require('nuclei/bytes');
+ * const buffer = new bytes.Buffer();
+ * buffer.WriteString('hello');
+ * log(buffer.Len());
+ * ```
+ */
+ public Len(): number {
+ return 0;
+ }
+
+
+ /**
+ * Hex returns the hex representation of the buffer.
+ * @example
+ * ```javascript
+ * const bytes = require('nuclei/bytes');
+ * const buffer = new bytes.Buffer();
+ * buffer.WriteString('hello');
+ * log(buffer.Hex());
+ * ```
+ */
+ public Hex(): string {
+ return "";
+ }
+
+
+ /**
+ * Hexdump returns the hexdump representation of the buffer.
+ * @example
+ * ```javascript
+ * const bytes = require('nuclei/bytes');
+ * const buffer = new bytes.Buffer();
+ * buffer.WriteString('hello');
+ * log(buffer.Hexdump());
+ * ```
+ */
+ public Hexdump(): string {
+ return "";
+ }
+
+
+ /**
+ * Pack uses structs.Pack and packs given data and appends it to the buffer.
+ * it packs the data according to the given format.
+ * @example
+ * ```javascript
+ * const bytes = require('nuclei/bytes');
+ * const buffer = new bytes.Buffer();
+ * buffer.Pack('I', 123);
+ * ```
+ */
+ public Pack(formatStr: string, msg: any): void {
+ return;
+ }
+
+
+}
+
diff --git a/pkg/js/generated/ts/fs.ts b/pkg/js/generated/ts/fs.ts
new file mode 100755
index 0000000000..234c31d61e
--- /dev/null
+++ b/pkg/js/generated/ts/fs.ts
@@ -0,0 +1,78 @@
+
+
+/**
+ * ListDir lists itemType values within a directory
+ * depending on the itemType provided
+ * itemType can be any one of ['file','dir',”]
+ * @example
+ * ```javascript
+ * const fs = require('nuclei/fs');
+ * // this will only return files in /tmp directory
+ * const files = fs.ListDir('/tmp', 'file');
+ * ```
+ * @example
+ * ```javascript
+ * const fs = require('nuclei/fs');
+ * // this will only return directories in /tmp directory
+ * const dirs = fs.ListDir('/tmp', 'dir');
+ * ```
+ * @example
+ * ```javascript
+ * const fs = require('nuclei/fs');
+ * // when no itemType is provided, it will return both files and directories
+ * const items = fs.ListDir('/tmp');
+ * ```
+ */
+export function ListDir(path: string, itemType: string): string[] | null {
+ return null;
+}
+
+
+
+/**
+ * ReadFile reads file contents within permitted paths
+ * and returns content as byte array
+ * @example
+ * ```javascript
+ * const fs = require('nuclei/fs');
+ * // here permitted directories are $HOME/nuclei-templates/*
+ * const content = fs.ReadFile('helpers/usernames.txt');
+ * ```
+ */
+export function ReadFile(path: string): Uint8Array | null {
+ return null;
+}
+
+
+
+/**
+ * ReadFileAsString reads file contents within permitted paths
+ * and returns content as string
+ * @example
+ * ```javascript
+ * const fs = require('nuclei/fs');
+ * // here permitted directories are $HOME/nuclei-templates/*
+ * const content = fs.ReadFileAsString('helpers/usernames.txt');
+ * ```
+ */
+export function ReadFileAsString(path: string): string | null {
+ return null;
+}
+
+
+
+/**
+ * ReadFilesFromDir reads all files from a directory
+ * and returns a string array with file contents of all files
+ * @example
+ * ```javascript
+ * const fs = require('nuclei/fs');
+ * // here permitted directories are $HOME/nuclei-templates/*
+ * const contents = fs.ReadFilesFromDir('helpers/ssh-keys');
+ * log(contents);
+ * ```
+ */
+export function ReadFilesFromDir(dir: string): string[] | null {
+ return null;
+}
+
diff --git a/pkg/js/generated/ts/goconsole.ts b/pkg/js/generated/ts/goconsole.ts
new file mode 100755
index 0000000000..f4bb707d3e
--- /dev/null
+++ b/pkg/js/generated/ts/goconsole.ts
@@ -0,0 +1,44 @@
+
+
+/**
+ * NewGoConsolePrinter Function
+ */
+export function NewGoConsolePrinter(): GoConsolePrinter {
+ return new GoConsolePrinter();
+}
+
+
+
+/**
+ */
+export class GoConsolePrinter {
+
+
+ // Constructor of GoConsolePrinter
+ constructor() {}
+ /**
+ * Log Method
+ */
+ public Log(msg: string): void {
+ return;
+ }
+
+
+ /**
+ * Warn Method
+ */
+ public Warn(msg: string): void {
+ return;
+ }
+
+
+ /**
+ * Error Method
+ */
+ public Error(msg: string): void {
+ return;
+ }
+
+
+}
+
diff --git a/pkg/js/generated/ts/ikev2.ts b/pkg/js/generated/ts/ikev2.ts
new file mode 100755
index 0000000000..d8283a3716
--- /dev/null
+++ b/pkg/js/generated/ts/ikev2.ts
@@ -0,0 +1,125 @@
+
+
+
+export const IKE_EXCHANGE_AUTH = 35;
+
+
+export const IKE_EXCHANGE_CREATE_CHILD_SA = 36;
+
+
+export const IKE_EXCHANGE_INFORMATIONAL = 37;
+
+
+export const IKE_EXCHANGE_SA_INIT = 34;
+
+
+export const IKE_FLAGS_InitiatorBitCheck = 0x08;
+
+
+export const IKE_NOTIFY_NO_PROPOSAL_CHOSEN = 14;
+
+
+export const IKE_NOTIFY_USE_TRANSPORT_MODE = 16391;
+
+
+export const IKE_VERSION_2 = 0x20;
+
+/**
+ * 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.
+ */
+export class IKEMessage {
+
+
+
+ public InitiatorSPI?: number;
+
+
+
+ public Version?: number;
+
+
+
+ public ExchangeType?: number;
+
+
+
+ public Flags?: number;
+
+
+ // Constructor of IKEMessage
+ constructor() {}
+ /**
+ * AppendPayload appends a payload to the IKE message
+ * payload can be any of the payloads like IKENotification, IKENonce, etc.
+ * @example
+ * ```javascript
+ * const ikev2 = require('nuclei/ikev2');
+ * const message = new ikev2.IKEMessage();
+ * const nonce = new ikev2.IKENonce();
+ * nonce.NonceData = [1, 2, 3];
+ * message.AppendPayload(nonce);
+ * ```
+ */
+ public AppendPayload(payload: any): void {
+ return;
+ }
+
+
+ /**
+ * Encode encodes the final IKE message
+ * @example
+ * ```javascript
+ * const ikev2 = require('nuclei/ikev2');
+ * const message = new ikev2.IKEMessage();
+ * const nonce = new ikev2.IKENonce();
+ * nonce.NonceData = [1, 2, 3];
+ * message.AppendPayload(nonce);
+ * log(message.Encode());
+ * ```
+ */
+ public Encode(): Uint8Array | null {
+ return null;
+ }
+
+
+}
+
+
+
+/**
+ * IKENonce is the IKEv2 Nonce payload
+ * this implements the IKEPayload interface
+ * @example
+ * ```javascript
+ * const ikev2 = require('nuclei/ikev2');
+ * const nonce = new ikev2.IKENonce();
+ * nonce.NonceData = [1, 2, 3];
+ * ```
+ */
+export interface IKENonce {
+
+ NonceData?: Uint8Array,
+}
+
+
+
+/**
+ * IKEv2Notify is the IKEv2 Notification payload
+ * this implements the IKEPayload interface
+ * @example
+ * ```javascript
+ * const ikev2 = require('nuclei/ikev2');
+ * const notify = new ikev2.IKENotification();
+ * notify.NotifyMessageType = ikev2.IKE_NOTIFY_NO_PROPOSAL_CHOSEN;
+ * notify.NotificationData = [1, 2, 3];
+ * ```
+ */
+export interface IKENotification {
+
+ NotifyMessageType?: number,
+
+ NotificationData?: Uint8Array,
+}
+
diff --git a/pkg/js/generated/ts/index.ts b/pkg/js/generated/ts/index.ts
new file mode 100755
index 0000000000..a4175a45b9
--- /dev/null
+++ b/pkg/js/generated/ts/index.ts
@@ -0,0 +1,21 @@
+export * as bytes from './bytes';
+export * as fs from './fs';
+export * as goconsole from './goconsole';
+export * as ikev2 from './ikev2';
+export * as kerberos from './kerberos';
+export * as ldap from './ldap';
+export * as mssql from './mssql';
+export * as mysql from './mysql';
+export * as net from './net';
+export * as oracle from './oracle';
+export * as pop3 from './pop3';
+export * as postgres from './postgres';
+export * as rdp from './rdp';
+export * as redis from './redis';
+export * as rsync from './rsync';
+export * as smb from './smb';
+export * as smtp from './smtp';
+export * as ssh from './ssh';
+export * as structs from './structs';
+export * as telnet from './telnet';
+export * as vnc from './vnc';
diff --git a/pkg/js/generated/ts/kerberos.ts b/pkg/js/generated/ts/kerberos.ts
new file mode 100755
index 0000000000..a142e536bb
--- /dev/null
+++ b/pkg/js/generated/ts/kerberos.ts
@@ -0,0 +1,475 @@
+
+
+/**
+ * ASRepToHashcat converts an AS-REP message to a hashcat format
+ */
+export function ASRepToHashcat(asrep: any): string | null {
+ return null;
+}
+
+
+
+/**
+ * CheckKrbError checks if the response bytes from the KDC are a KRBError.
+ */
+export function CheckKrbError(b: Uint8Array): Uint8Array | null {
+ return null;
+}
+
+
+
+/**
+ * NewKerberosClientFromString creates a new kerberos client from a string
+ * by parsing krb5.conf
+ * @example
+ * ```javascript
+ * const kerberos = require('nuclei/kerberos');
+ * const client = kerberos.NewKerberosClientFromString(`
+ * [libdefaults]
+ * default_realm = ACME.COM
+ * dns_lookup_kdc = true
+ * `);
+ * ```
+ */
+export function NewKerberosClientFromString(cfg: string): Client | null {
+ return null;
+}
+
+
+
+/**
+ * sendtokdc.go deals with actual sending and receiving responses from KDC
+ * SendToKDC sends a message to the KDC and returns the response.
+ * It first tries to send the message over TCP, and if that fails, it falls back to UDP.(and vice versa)
+ * @example
+ * ```javascript
+ * const kerberos = require('nuclei/kerberos');
+ * const client = new kerberos.Client('acme.com');
+ * const response = kerberos.SendToKDC(client, 'message');
+ * ```
+ */
+export function SendToKDC(kclient: Client, msg: string): string | null {
+ return null;
+}
+
+
+
+/**
+ * TGStoHashcat converts a TGS to a hashcat format.
+ */
+export function TGStoHashcat(tgs: any, username: string): string | null {
+ return null;
+}
+
+
+
+/**
+ * Known Issues:
+ * Hardcoded timeout in gokrb5 library
+ * TGT / Session Handling not exposed
+ * Client is kerberos client
+ * @example
+ * ```javascript
+ * const kerberos = require('nuclei/kerberos');
+ * // if controller is empty a dns lookup for default kdc server will be performed
+ * const client = new kerberos.Client('acme.com', 'kdc.acme.com');
+ * ```
+ */
+export class Client {
+
+
+
+ public Krb5Config?: Config;
+
+
+
+ public Realm?: string;
+
+
+ // Constructor of Client
+ constructor(public domain: string, public controller?: string ) {}
+
+
+ /**
+ * SetConfig sets additional config for the kerberos client
+ * Note: as of now ip and timeout overrides are only supported
+ * in EnumerateUser due to fastdialer but can be extended to other methods currently
+ * @example
+ * ```javascript
+ * const kerberos = require('nuclei/kerberos');
+ * const client = new kerberos.Client('acme.com', 'kdc.acme.com');
+ * const cfg = new kerberos.Config();
+ * cfg.SetIPAddress('192.168.100.22');
+ * cfg.SetTimeout(5);
+ * client.SetConfig(cfg);
+ * ```
+ */
+ public SetConfig(cfg: Config): void {
+ return;
+ }
+
+
+ /**
+ * EnumerateUser and attempt to get AS-REP hash by disabling PA-FX-FAST
+ * @example
+ * ```javascript
+ * const kerberos = require('nuclei/kerberos');
+ * const client = new kerberos.Client('acme.com', 'kdc.acme.com');
+ * const resp = client.EnumerateUser('pdtm');
+ * log(resp);
+ * ```
+ */
+ public EnumerateUser(username: string): EnumerateUserResponse | null {
+ return null;
+ }
+
+
+ /**
+ * GetServiceTicket returns a TGS for a given user, password and SPN
+ * @example
+ * ```javascript
+ * const kerberos = require('nuclei/kerberos');
+ * const client = new kerberos.Client('acme.com', 'kdc.acme.com');
+ * const resp = client.GetServiceTicket('pdtm', 'password', 'HOST/CLIENT1');
+ * log(resp);
+ * ```
+ */
+ public GetServiceTicket(User: string): TGS | null {
+ return null;
+ }
+
+
+}
+
+
+
+/**
+ * Config is extra configuration for the kerberos client
+ */
+export class Config {
+
+
+ // Constructor of Config
+ constructor() {}
+ /**
+ * SetIPAddress sets the IP address for the kerberos client
+ * @example
+ * ```javascript
+ * const kerberos = require('nuclei/kerberos');
+ * const cfg = new kerberos.Config();
+ * cfg.SetIPAddress('10.10.10.1');
+ * ```
+ */
+ public SetIPAddress(ip: string): Config | null {
+ return null;
+ }
+
+
+ /**
+ * SetTimeout sets the RW timeout for the kerberos client
+ * @example
+ * ```javascript
+ * const kerberos = require('nuclei/kerberos');
+ * const cfg = new kerberos.Config();
+ * cfg.SetTimeout(5);
+ * ```
+ */
+ public SetTimeout(timeout: number): Config | null {
+ return null;
+ }
+
+
+}
+
+
+
+/**
+ * AuthorizationDataEntry Interface
+ */
+export interface AuthorizationDataEntry {
+
+ ADType?: number,
+
+ ADData?: Uint8Array,
+}
+
+
+
+/**
+ * BitString Interface
+ */
+export interface BitString {
+
+ Bytes?: Uint8Array,
+
+ BitLength?: number,
+}
+
+
+
+/**
+ * BitString Interface
+ */
+export interface BitString {
+
+ Bytes?: Uint8Array,
+
+ BitLength?: number,
+}
+
+
+
+/**
+ * Config Interface
+ */
+export interface Config {
+
+ LibDefaults?: LibDefaults,
+
+ Realms?: Realm,
+}
+
+
+
+/**
+ * EncTicketPart Interface
+ */
+export interface EncTicketPart {
+
+ RenewTill?: Date,
+
+ CRealm?: string,
+
+ AuthTime?: Date,
+
+ StartTime?: Date,
+
+ EndTime?: Date,
+
+ Transited?: TransitedEncoding,
+
+ CAddr?: HostAddress,
+
+ AuthorizationData?: AuthorizationDataEntry,
+
+ Flags?: BitString,
+
+ Key?: EncryptionKey,
+
+ CName?: PrincipalName,
+}
+
+
+
+/**
+ * EncryptedData Interface
+ */
+export interface EncryptedData {
+
+ Cipher?: Uint8Array,
+
+ EType?: number,
+
+ KVNO?: number,
+}
+
+
+
+/**
+ * EncryptionKey Interface
+ */
+export interface EncryptionKey {
+
+ KeyType?: number,
+
+ KeyValue?: Uint8Array,
+}
+
+
+
+/**
+ * EnumerateUserResponse is the response from EnumerateUser
+ */
+export interface EnumerateUserResponse {
+
+ Valid?: boolean,
+
+ ASREPHash?: string,
+
+ Error?: string,
+}
+
+
+
+/**
+ * HostAddress Interface
+ */
+export interface HostAddress {
+
+ AddrType?: number,
+
+ Address?: Uint8Array,
+}
+
+
+
+/**
+ * LibDefaults Interface
+ */
+export interface LibDefaults {
+
+ /**
+ * time in nanoseconds
+ */
+
+ Clockskew?: number,
+
+ KDCTimeSync?: number,
+
+ SafeChecksumType?: number,
+
+ /**
+ * time in nanoseconds
+ */
+
+ TicketLifetime?: number,
+
+ Forwardable?: boolean,
+
+ K5LoginAuthoritative?: boolean,
+
+ AllowWeakCrypto?: boolean,
+
+ DefaultClientKeytabName?: string,
+
+ DefaultTktEnctypes?: string[],
+
+ ExtraAddresses?: Uint8Array,
+
+ K5LoginDirectory?: string,
+
+ PreferredPreauthTypes?: number[],
+
+ RDNS?: boolean,
+
+ DefaultKeytabName?: string,
+
+ DefaultRealm?: string,
+
+ DefaultTGSEnctypeIDs?: number[],
+
+ DNSCanonicalizeHostname?: boolean,
+
+ PermittedEnctypes?: string[],
+
+ VerifyAPReqNofail?: boolean,
+
+ DNSLookupRealm?: boolean,
+
+ UDPPreferenceLimit?: number,
+
+ Canonicalize?: boolean,
+
+ CCacheType?: number,
+
+ DefaultTGSEnctypes?: string[],
+
+ Proxiable?: boolean,
+
+ DNSLookupKDC?: boolean,
+
+ RealmTryDomains?: number,
+
+ /**
+ * time in nanoseconds
+ */
+
+ RenewLifetime?: number,
+
+ DefaultTktEnctypeIDs?: number[],
+
+ IgnoreAcceptorHostname?: boolean,
+
+ NoAddresses?: boolean,
+
+ PermittedEnctypeIDs?: number[],
+
+ KDCDefaultOptions?: BitString,
+}
+
+
+
+/**
+ * PrincipalName Interface
+ */
+export interface PrincipalName {
+
+ NameType?: number,
+
+ NameString?: string[],
+}
+
+
+
+/**
+ * Realm Interface
+ */
+export interface Realm {
+
+ Realm?: string,
+
+ AdminServer?: string[],
+
+ DefaultDomain?: string,
+
+ KDC?: string[],
+
+ KPasswdServer?: string[],
+
+ MasterKDC?: string[],
+}
+
+
+
+/**
+ * TGS is the response from GetServiceTicket
+ */
+export interface TGS {
+
+ Ticket?: Ticket,
+
+ Hash?: string,
+
+ ErrMsg?: string,
+}
+
+
+
+/**
+ * Ticket Interface
+ */
+export interface Ticket {
+
+ Realm?: string,
+
+ TktVNO?: number,
+
+ SName?: PrincipalName,
+
+ EncPart?: EncryptedData,
+
+ DecryptedEncPart?: EncTicketPart,
+}
+
+
+
+/**
+ * TransitedEncoding Interface
+ */
+export interface TransitedEncoding {
+
+ TRType?: number,
+
+ Contents?: Uint8Array,
+}
+
diff --git a/pkg/js/generated/ts/ldap.ts b/pkg/js/generated/ts/ldap.ts
new file mode 100755
index 0000000000..5d25faa1c8
--- /dev/null
+++ b/pkg/js/generated/ts/ldap.ts
@@ -0,0 +1,579 @@
+
+
+/** The user account is disabled. */
+export const FilterAccountDisabled = "(userAccountControl:1.2.840.113556.1.4.803:=2)";
+
+/** The user account is enabled. */
+export const FilterAccountEnabled = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))";
+
+/** The user can send an encrypted password. */
+export const FilterCanSendEncryptedPassword = "(userAccountControl:1.2.840.113556.1.4.803:=128)";
+
+/** Represents the password, which should never expire on the account. */
+export const FilterDontExpirePassword = "(userAccountControl:1.2.840.113556.1.4.803:=65536)";
+
+/** This account doesn't require Kerberos pre-authentication for logging on. */
+export const FilterDontRequirePreauth = "(userAccountControl:1.2.840.113556.1.4.803:=4194304)";
+
+/** The object has a service principal name. */
+export const FilterHasServicePrincipalName = "(servicePrincipalName=*)";
+
+/** The home folder is required. */
+export const FilterHomedirRequired = "(userAccountControl:1.2.840.113556.1.4.803:=8)";
+
+/** It's a permit to trust an account for a system domain that trusts other domains. */
+export const FilterInterdomainTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=2048)";
+
+/** The object is an admin. */
+export const FilterIsAdmin = "(adminCount=1)";
+
+/** The object is a computer. */
+export const FilterIsComputer = "(objectCategory=computer)";
+
+/** It's an account for users whose primary account is in another domain. */
+export const FilterIsDuplicateAccount = "(userAccountControl:1.2.840.113556.1.4.803:=256)";
+
+/** The object is a group. */
+export const FilterIsGroup = "(objectCategory=group)";
+
+/** It's a default account type that represents a typical user. */
+export const FilterIsNormalAccount = "(userAccountControl:1.2.840.113556.1.4.803:=512)";
+
+/** The object is a person. */
+export const FilterIsPerson = "(objectCategory=person)";
+
+/** The user is locked out. */
+export const FilterLockout = "(userAccountControl:1.2.840.113556.1.4.803:=16)";
+
+/** The logon script will be run. */
+export const FilterLogonScript = "(userAccountControl:1.2.840.113556.1.4.803:=1)";
+
+/** It's an MNS logon account. */
+export const FilterMnsLogonAccount = "(userAccountControl:1.2.840.113556.1.4.803:=131072)";
+
+/** When this flag is set, the security context of the user isn't delegated to a service even if the service account is set as trusted for Kerberos delegation. */
+export const FilterNotDelegated = "(userAccountControl:1.2.840.113556.1.4.803:=1048576)";
+
+/** The account is a read-only domain controller (RODC). */
+export const FilterPartialSecretsAccount = "(userAccountControl:1.2.840.113556.1.4.803:=67108864)";
+
+/** The user can't change the password. */
+export const FilterPasswordCantChange = "(userAccountControl:1.2.840.113556.1.4.803:=64)";
+
+/** The user's password has expired. */
+export const FilterPasswordExpired = "(userAccountControl:1.2.840.113556.1.4.803:=8388608)";
+
+/** No password is required. */
+export const FilterPasswordNotRequired = "(userAccountControl:1.2.840.113556.1.4.803:=32)";
+
+/** It's a computer account for a domain controller that is a member of this domain. */
+export const FilterServerTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=8192)";
+
+/** When this flag is set, it forces the user to log on by using a smart card. */
+export const FilterSmartCardRequired = "(userAccountControl:1.2.840.113556.1.4.803:=262144)";
+
+/** When this flag is set, the service account (the user or computer account) under which a service runs is trusted for Kerberos delegation. */
+export const FilterTrustedForDelegation = "(userAccountControl:1.2.840.113556.1.4.803:=524288)";
+
+/** The account is enabled for delegation. */
+export const FilterTrustedToAuthForDelegation = "(userAccountControl:1.2.840.113556.1.4.803:=16777216)";
+
+/** Restrict this principal to use only Data Encryption Standard (DES) encryption types for keys. */
+export const FilterUseDesKeyOnly = "(userAccountControl:1.2.840.113556.1.4.803:=2097152)";
+
+/** It's a computer account for a computer that is running old Windows builds. */
+export const FilterWorkstationTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=4096)";
+
+/**
+ * DecodeADTimestamp decodes an Active Directory timestamp
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const timestamp = ldap.DecodeADTimestamp('132036744000000000');
+ * log(timestamp);
+ * ```
+ */
+export function DecodeADTimestamp(timestamp: string): string {
+ return "";
+}
+
+
+
+/**
+ * DecodeSID decodes a SID string
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const sid = ldap.DecodeSID('S-1-5-21-3623811015-3361044348-30300820-1013');
+ * log(sid);
+ * ```
+ */
+export function DecodeSID(s: string): string {
+ return "";
+}
+
+
+
+/**
+ * DecodeZuluTimestamp decodes a Zulu timestamp
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const timestamp = ldap.DecodeZuluTimestamp('2021-08-25T10:00:00Z');
+ * log(timestamp);
+ * ```
+ */
+export function DecodeZuluTimestamp(timestamp: string): string {
+ return "";
+}
+
+
+
+/**
+ * JoinFilters joins multiple filters into a single filter
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const filter = ldap.JoinFilters(ldap.FilterIsPerson, ldap.FilterAccountEnabled);
+ * ```
+ */
+export function JoinFilters(filters: any): string {
+ return "";
+}
+
+
+
+/**
+ * NegativeFilter returns a negative filter for a given filter
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const filter = ldap.NegativeFilter(ldap.FilterIsPerson);
+ * ```
+ */
+export function NegativeFilter(filter: string): string {
+ return "";
+}
+
+
+
+/**
+ * Client is a client for ldap protocol in nuclei
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * // here ldap.example.com is the ldap server and acme.com is the realm
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * ```
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const cfg = new ldap.Config();
+ * cfg.Timeout = 10;
+ * cfg.ServerName = 'ldap.internal.acme.com';
+ * // optional config can be passed as third argument
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com', cfg);
+ * ```
+ */
+export class Client {
+
+
+
+ public Host?: string;
+
+
+
+ public Port?: number;
+
+
+
+ public Realm?: string;
+
+
+
+ public BaseDN?: string;
+
+
+ // Constructor of Client
+ constructor(public ldapUrl: string, public realm: string, public config?: Config ) {}
+
+
+ /**
+ * FindADObjects finds AD objects based on a filter
+ * and returns them as a list of ADObject
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const users = client.FindADObjects(ldap.FilterIsPerson);
+ * log(to_json(users));
+ * ```
+ */
+ public FindADObjects(filter: string): ADObject[] {
+ return [];
+ }
+
+
+ /**
+ * GetADUsers returns all AD users
+ * using FilterIsPerson filter query
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const users = client.GetADUsers();
+ * log(to_json(users));
+ * ```
+ */
+ public GetADUsers(): ADObject[] {
+ return [];
+ }
+
+
+ /**
+ * GetADActiveUsers returns all AD users
+ * using FilterIsPerson and FilterAccountEnabled filter query
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const users = client.GetADActiveUsers();
+ * log(to_json(users));
+ * ```
+ */
+ public GetADActiveUsers(): ADObject[] {
+ return [];
+ }
+
+
+ /**
+ * GetAdUserWithNeverExpiringPasswords returns all AD users
+ * using FilterIsPerson and FilterDontExpirePassword filter query
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const users = client.GetADUserWithNeverExpiringPasswords();
+ * log(to_json(users));
+ * ```
+ */
+ public GetADUserWithNeverExpiringPasswords(): ADObject[] {
+ return [];
+ }
+
+
+ /**
+ * GetADUserTrustedForDelegation returns all AD users that are trusted for delegation
+ * using FilterIsPerson and FilterTrustedForDelegation filter query
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const users = client.GetADUserTrustedForDelegation();
+ * log(to_json(users));
+ * ```
+ */
+ public GetADUserTrustedForDelegation(): ADObject[] {
+ return [];
+ }
+
+
+ /**
+ * GetADUserWithPasswordNotRequired returns all AD users that do not require a password
+ * using FilterIsPerson and FilterPasswordNotRequired filter query
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const users = client.GetADUserWithPasswordNotRequired();
+ * log(to_json(users));
+ * ```
+ */
+ public GetADUserWithPasswordNotRequired(): ADObject[] {
+ return [];
+ }
+
+
+ /**
+ * GetADGroups returns all AD groups
+ * using FilterIsGroup filter query
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const groups = client.GetADGroups();
+ * log(to_json(groups));
+ * ```
+ */
+ public GetADGroups(): ADObject[] {
+ return [];
+ }
+
+
+ /**
+ * GetADDCList returns all AD domain controllers
+ * using FilterIsComputer, FilterAccountEnabled and FilterServerTrustAccount filter query
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const dcs = client.GetADDCList();
+ * log(to_json(dcs));
+ * ```
+ */
+ public GetADDCList(): ADObject[] {
+ return [];
+ }
+
+
+ /**
+ * GetADAdmins returns all AD admins
+ * using FilterIsPerson, FilterAccountEnabled and FilterIsAdmin filter query
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const admins = client.GetADAdmins();
+ * log(to_json(admins));
+ * ```
+ */
+ public GetADAdmins(): ADObject[] {
+ return [];
+ }
+
+
+ /**
+ * GetADUserKerberoastable returns all AD users that are kerberoastable
+ * using FilterIsPerson, FilterAccountEnabled and FilterHasServicePrincipalName filter query
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const kerberoastable = client.GetADUserKerberoastable();
+ * log(to_json(kerberoastable));
+ * ```
+ */
+ public GetADUserKerberoastable(): ADObject[] {
+ return [];
+ }
+
+
+ /**
+ * GetADDomainSID returns the SID of the AD domain
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const domainSID = client.GetADDomainSID();
+ * log(domainSID);
+ * ```
+ */
+ public GetADDomainSID(): string {
+ return "";
+ }
+
+
+ /**
+ * Authenticate authenticates with the ldap server using the given username and password
+ * performs NTLMBind first and then Bind/UnauthenticatedBind if NTLMBind fails
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * client.Authenticate('user', 'password');
+ * ```
+ */
+ public Authenticate(username: string): void {
+ return;
+ }
+
+
+ /**
+ * AuthenticateWithNTLMHash authenticates with the ldap server using the given username and NTLM hash
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * client.AuthenticateWithNTLMHash('pdtm', 'hash');
+ * ```
+ */
+ public AuthenticateWithNTLMHash(username: string): void {
+ return;
+ }
+
+
+ /**
+ * Search accepts whatever filter and returns a list of maps having provided attributes
+ * as keys and associated values mirroring the ones returned by ldap
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const results = client.Search('(objectClass=*)', 'cn', 'mail');
+ * ```
+ */
+ public Search(filter: string, attributes: any): Record[] {
+ return [];
+ }
+
+
+ /**
+ * AdvancedSearch accepts all values of search request type and return Ldap Entry
+ * its up to user to handle the response
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const results = client.AdvancedSearch(ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, '(objectClass=*)', ['cn', 'mail'], []);
+ * ```
+ */
+ public AdvancedSearch(Scope: number, TypesOnly: boolean, Filter: string, Attributes: string[], Controls: any): SearchResult | null {
+ return null;
+ }
+
+
+ /**
+ * CollectLdapMetadata collects metadata from ldap server.
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const metadata = client.CollectMetadata();
+ * log(to_json(metadata));
+ * ```
+ */
+ public CollectMetadata(): Metadata | null {
+ return null;
+ }
+
+
+ /**
+ * close the ldap connection
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * client.Close();
+ * ```
+ */
+ public Close(): void {
+ return;
+ }
+
+
+}
+
+
+
+/**
+ * ADObject represents an Active Directory object
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ * const users = client.GetADUsers();
+ * log(to_json(users));
+ * ```
+ */
+export interface ADObject {
+
+ DistinguishedName?: string,
+
+ SAMAccountName?: string,
+
+ PWDLastSet?: string,
+
+ LastLogon?: string,
+
+ MemberOf?: string[],
+
+ ServicePrincipalName?: string[],
+}
+
+
+
+/**
+ * Config is extra configuration for the ldap client
+ * @example
+ * ```javascript
+ * const ldap = require('nuclei/ldap');
+ * const cfg = new ldap.Config();
+ * cfg.Timeout = 10;
+ * cfg.ServerName = 'ldap.internal.acme.com';
+ * cfg.Upgrade = true; // upgrade to tls
+ * ```
+ */
+export interface Config {
+
+ /**
+ * Timeout is the timeout for the ldap client in seconds
+ */
+
+ Timeout?: number,
+
+ ServerName?: string,
+
+ Upgrade?: boolean,
+}
+
+
+
+/**
+ * Entry Interface
+ */
+export interface Entry {
+
+ DN?: string,
+
+ Attributes?: EntryAttribute,
+}
+
+
+
+/**
+ * EntryAttribute Interface
+ */
+export interface EntryAttribute {
+
+ Name?: string,
+
+ Values?: string[],
+
+ ByteValues?: Uint8Array,
+}
+
+
+
+/**
+ * Metadata is the metadata for ldap server.
+ * this is returned by CollectMetadata method
+ */
+export interface Metadata {
+
+ BaseDN?: string,
+
+ Domain?: string,
+
+ DefaultNamingContext?: string,
+
+ DomainFunctionality?: string,
+
+ ForestFunctionality?: string,
+
+ DomainControllerFunctionality?: string,
+
+ DnsHostName?: string,
+}
+
+
+
+/**
+ * SearchResult Interface
+ */
+export interface SearchResult {
+
+ Referrals?: string[],
+
+ Entries?: Entry,
+}
+
diff --git a/pkg/js/generated/ts/mssql.ts b/pkg/js/generated/ts/mssql.ts
new file mode 100755
index 0000000000..bff2edf8b5
--- /dev/null
+++ b/pkg/js/generated/ts/mssql.ts
@@ -0,0 +1,67 @@
+
+
+/**
+ * Client is a client for MS SQL database.
+ * Internally client uses denisenkom/go-mssqldb driver.
+ * @example
+ * ```javascript
+ * const mssql = require('nuclei/mssql');
+ * const client = new mssql.MSSQLClient;
+ * ```
+ */
+export class MSSQLClient {
+
+
+ // Constructor of MSSQLClient
+ constructor() {}
+ /**
+ * 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.
+ * @example
+ * ```javascript
+ * const mssql = require('nuclei/mssql');
+ * const client = new mssql.MSSQLClient;
+ * const connected = client.Connect('acme.com', 1433, 'username', 'password');
+ * ```
+ */
+ public Connect(host: string, port: number, username: string): boolean | null {
+ return null;
+ }
+
+
+ /**
+ * 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.
+ * @example
+ * ```javascript
+ * const mssql = require('nuclei/mssql');
+ * const client = new mssql.MSSQLClient;
+ * const connected = client.ConnectWithDB('acme.com', 1433, 'username', 'password', 'master');
+ * ```
+ */
+ public ConnectWithDB(host: string, port: number, username: string): boolean | null {
+ return null;
+ }
+
+
+ /**
+ * 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.
+ * @example
+ * ```javascript
+ * const mssql = require('nuclei/mssql');
+ * const isMssql = mssql.IsMssql('acme.com', 1433);
+ * ```
+ */
+ public IsMssql(host: string, port: number): boolean | null {
+ return null;
+ }
+
+
+}
+
diff --git a/pkg/js/generated/ts/mysql.ts b/pkg/js/generated/ts/mysql.ts
new file mode 100755
index 0000000000..a8c331cde0
--- /dev/null
+++ b/pkg/js/generated/ts/mysql.ts
@@ -0,0 +1,230 @@
+
+
+/**
+ * BuildDSN builds a MySQL data source name (DSN) from the given options.
+ * @example
+ * ```javascript
+ * const mysql = require('nuclei/mysql');
+ * const options = new mysql.MySQLOptions();
+ * options.Host = 'acme.com';
+ * options.Port = 3306;
+ * const dsn = mysql.BuildDSN(options);
+ * ```
+ */
+export function BuildDSN(opts: MySQLOptions): string | null {
+ return null;
+}
+
+
+
+/**
+ * MySQLClient is a client for MySQL database.
+ * Internally client uses go-sql-driver/mysql driver.
+ * @example
+ * ```javascript
+ * const mysql = require('nuclei/mysql');
+ * const client = new mysql.MySQLClient;
+ * ```
+ */
+export class MySQLClient {
+
+
+ // Constructor of MySQLClient
+ constructor() {}
+ /**
+ * 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.
+ * @example
+ * ```javascript
+ * const mysql = require('nuclei/mysql');
+ * const isMySQL = mysql.IsMySQL('acme.com', 3306);
+ * ```
+ */
+ public IsMySQL(host: string, port: number): boolean | null {
+ return null;
+ }
+
+
+ /**
+ * 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.
+ * @example
+ * ```javascript
+ * const mysql = require('nuclei/mysql');
+ * const client = new mysql.MySQLClient;
+ * const connected = client.Connect('acme.com', 3306, 'username', 'password');
+ * ```
+ */
+ public Connect(host: string, port: number, username: string): boolean | null {
+ return null;
+ }
+
+
+ /**
+ * returns MySQLInfo when fingerpint is successful
+ * @example
+ * ```javascript
+ * const mysql = require('nuclei/mysql');
+ * const info = mysql.FingerprintMySQL('acme.com', 3306);
+ * log(to_json(info));
+ * ```
+ */
+ public FingerprintMySQL(host: string, port: number): MySQLInfo | null {
+ return null;
+ }
+
+
+ /**
+ * ConnectWithDSN connects to MySQL database using given DSN.
+ * we override mysql dialer with fastdialer so it respects network policy
+ * If connection is successful, it returns true.
+ * @example
+ * ```javascript
+ * const mysql = require('nuclei/mysql');
+ * const client = new mysql.MySQLClient;
+ * const connected = client.ConnectWithDSN('username:password@tcp(acme.com:3306)/');
+ * ```
+ */
+ public ConnectWithDSN(dsn: string): boolean | null {
+ return null;
+ }
+
+
+ /**
+ * ExecuteQueryWithOpts connects to Mysql database using given credentials
+ * and executes a query on the db.
+ * @example
+ * ```javascript
+ * const mysql = require('nuclei/mysql');
+ * const options = new mysql.MySQLOptions();
+ * options.Host = 'acme.com';
+ * options.Port = 3306;
+ * const result = mysql.ExecuteQueryWithOpts(options, 'SELECT * FROM users');
+ * log(to_json(result));
+ * ```
+ */
+ public ExecuteQueryWithOpts(opts: MySQLOptions, query: string): SQLResult | null | null {
+ return null;
+ }
+
+
+ /**
+ * ExecuteQuery connects to Mysql database using given credentials
+ * and executes a query on the db.
+ * @example
+ * ```javascript
+ * const mysql = require('nuclei/mysql');
+ * const result = mysql.ExecuteQuery('acme.com', 3306, 'username', 'password', 'SELECT * FROM users');
+ * log(to_json(result));
+ * ```
+ */
+ public ExecuteQuery(host: string, port: number, username: string): SQLResult | null | null {
+ return null;
+ }
+
+
+ /**
+ * ExecuteQuery connects to Mysql database using given credentials
+ * and executes a query on the db.
+ * @example
+ * ```javascript
+ * const mysql = require('nuclei/mysql');
+ * const result = mysql.ExecuteQueryOnDB('acme.com', 3306, 'username', 'password', 'dbname', 'SELECT * FROM users');
+ * log(to_json(result));
+ * ```
+ */
+ public ExecuteQueryOnDB(host: string, port: number, username: string): SQLResult | null | null {
+ return null;
+ }
+
+
+}
+
+
+
+/**
+ * MySQLInfo contains information about MySQL server.
+ * this is returned when fingerprint is successful
+ */
+export interface MySQLInfo {
+
+ Host?: string,
+
+ IP?: string,
+
+ Port?: number,
+
+ Protocol?: string,
+
+ TLS?: boolean,
+
+ Transport?: string,
+
+ Version?: string,
+
+ Debug?: ServiceMySQL,
+
+ Raw?: string,
+}
+
+
+
+/**
+ * MySQLOptions defines the data source name (DSN) options required to connect to a MySQL database.
+ * along with other options like Timeout etc
+ * @example
+ * ```javascript
+ * const mysql = require('nuclei/mysql');
+ * const options = new mysql.MySQLOptions();
+ * options.Host = 'acme.com';
+ * options.Port = 3306;
+ * ```
+ */
+export interface MySQLOptions {
+
+ Host?: string,
+
+ Port?: number,
+
+ Protocol?: string,
+
+ Username?: string,
+
+ Password?: string,
+
+ DbName?: string,
+
+ RawQuery?: string,
+
+ Timeout?: number,
+}
+
+
+
+/**
+ * SQLResult Interface
+ */
+export interface SQLResult {
+
+ Columns?: string[],
+
+ Count?: number,
+}
+
+
+
+/**
+ * ServiceMySQL Interface
+ */
+export interface ServiceMySQL {
+
+ PacketType?: string,
+
+ ErrorMessage?: string,
+
+ ErrorCode?: number,
+}
+
diff --git a/pkg/js/generated/ts/net.ts b/pkg/js/generated/ts/net.ts
new file mode 100755
index 0000000000..f68846e557
--- /dev/null
+++ b/pkg/js/generated/ts/net.ts
@@ -0,0 +1,219 @@
+
+
+/**
+ * Open opens a new connection to the address with a timeout.
+ * supported protocols: tcp, udp
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * ```
+ */
+export function Open(protocol: string): NetConn | null {
+ return null;
+}
+
+
+
+/**
+ * Open opens a new connection to the address with a timeout.
+ * supported protocols: tcp, udp
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.OpenTLS('tcp', 'acme.com:443');
+ * ```
+ */
+export function OpenTLS(protocol: string): NetConn | null {
+ return null;
+}
+
+
+
+/**
+ * NetConn is a connection to a remote host.
+ * this is returned/create by Open and OpenTLS functions.
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * ```
+ */
+export class NetConn {
+
+
+ // Constructor of NetConn
+ constructor() {}
+ /**
+ * Close closes the connection.
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * conn.Close();
+ * ```
+ */
+ public Close(): void {
+ return;
+ }
+
+
+ /**
+ * SetTimeout sets read/write timeout for the connection (in seconds).
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * conn.SetTimeout(10);
+ * ```
+ */
+ public SetTimeout(value: number): void {
+ return;
+ }
+
+
+ /**
+ * SendArray sends array data to connection
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * conn.SendArray(['hello', 'world']);
+ * ```
+ */
+ public SendArray(data: any): void {
+ return;
+ }
+
+
+ /**
+ * SendHex sends hex data to connection
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * conn.SendHex('68656c6c6f');
+ * ```
+ */
+ public SendHex(data: string): void {
+ return;
+ }
+
+
+ /**
+ * Send sends data to the connection with a timeout.
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * conn.Send('hello');
+ * ```
+ */
+ public Send(data: string): void {
+ return;
+ }
+
+
+ /**
+ * RecvFull receives data from the connection with a timeout.
+ * If N is 0, it will read all data sent by the server with 8MB limit.
+ * it tries to read until N bytes or timeout is reached.
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * const data = conn.RecvFull(1024);
+ * ```
+ */
+ public RecvFull(N: number): Uint8Array | null {
+ return null;
+ }
+
+
+ /**
+ * Recv is similar to RecvFull but does not guarantee full read instead
+ * it creates a buffer of N bytes and returns whatever is returned by the connection
+ * for reading headers or initial bytes from the server this is usually used.
+ * for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * const data = conn.Recv(1024);
+ * log(`Received ${data.length} bytes from the server`)
+ * ```
+ */
+ public Recv(N: number): Uint8Array | null {
+ return null;
+ }
+
+
+ /**
+ * RecvFullString receives data from the connection with a timeout
+ * output is returned as a string.
+ * If N is 0, it will read all data sent by the server with 8MB limit.
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * const data = conn.RecvFullString(1024);
+ * ```
+ */
+ public RecvFullString(N: number): string | null {
+ return null;
+ }
+
+
+ /**
+ * RecvString is similar to RecvFullString but does not guarantee full read, instead
+ * it creates a buffer of N bytes and returns whatever is returned by the connection
+ * for reading headers or initial bytes from the server this is usually used.
+ * for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFullString.
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * const data = conn.RecvString(1024);
+ * ```
+ */
+ public RecvString(N: number): string | null {
+ return null;
+ }
+
+
+ /**
+ * RecvFullHex receives data from the connection with a timeout
+ * in hex format.
+ * If N is 0,it will read all data sent by the server with 8MB limit.
+ * until N bytes or timeout is reached.
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * const data = conn.RecvFullHex(1024);
+ * ```
+ */
+ public RecvFullHex(N: number): string | null {
+ return null;
+ }
+
+
+ /**
+ * RecvHex is similar to RecvFullHex but does not guarantee full read instead
+ * it creates a buffer of N bytes and returns whatever is returned by the connection
+ * for reading headers or initial bytes from the server this is usually used.
+ * for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.
+ * @example
+ * ```javascript
+ * const net = require('nuclei/net');
+ * const conn = net.Open('tcp', 'acme.com:80');
+ * const data = conn.RecvHex(1024);
+ * ```
+ */
+ public RecvHex(N: number): string | null {
+ return null;
+ }
+
+
+}
+
diff --git a/pkg/js/generated/ts/oracle.ts b/pkg/js/generated/ts/oracle.ts
new file mode 100755
index 0000000000..852e919e7b
--- /dev/null
+++ b/pkg/js/generated/ts/oracle.ts
@@ -0,0 +1,33 @@
+
+
+/**
+ * IsOracle checks if a host is running an Oracle server
+ * @example
+ * ```javascript
+ * const oracle = require('nuclei/oracle');
+ * const isOracle = oracle.IsOracle('acme.com', 1521);
+ * log(toJSON(isOracle));
+ * ```
+ */
+export function IsOracle(host: string, port: number): IsOracleResponse | null {
+ return null;
+}
+
+
+
+/**
+ * IsOracleResponse is the response from the IsOracle function.
+ * this is returned by IsOracle function.
+ * @example
+ * ```javascript
+ * const oracle = require('nuclei/oracle');
+ * const isOracle = oracle.IsOracle('acme.com', 1521);
+ * ```
+ */
+export interface IsOracleResponse {
+
+ IsOracle?: boolean,
+
+ Banner?: string,
+}
+
diff --git a/pkg/js/generated/ts/pop3.ts b/pkg/js/generated/ts/pop3.ts
new file mode 100755
index 0000000000..84de1718a4
--- /dev/null
+++ b/pkg/js/generated/ts/pop3.ts
@@ -0,0 +1,34 @@
+
+
+/**
+ * IsPOP3 checks if a host is running a POP3 server.
+ * @example
+ * ```javascript
+ * const pop3 = require('nuclei/pop3');
+ * const isPOP3 = pop3.IsPOP3('acme.com', 110);
+ * log(toJSON(isPOP3));
+ * ```
+ */
+export function IsPOP3(host: string, port: number): IsPOP3Response | null {
+ return null;
+}
+
+
+
+/**
+ * IsPOP3Response is the response from the IsPOP3 function.
+ * this is returned by IsPOP3 function.
+ * @example
+ * ```javascript
+ * const pop3 = require('nuclei/pop3');
+ * const isPOP3 = pop3.IsPOP3('acme.com', 110);
+ * log(toJSON(isPOP3));
+ * ```
+ */
+export interface IsPOP3Response {
+
+ IsPOP3?: boolean,
+
+ Banner?: string,
+}
+
diff --git a/pkg/js/generated/ts/postgres.ts b/pkg/js/generated/ts/postgres.ts
new file mode 100755
index 0000000000..31efd44c33
--- /dev/null
+++ b/pkg/js/generated/ts/postgres.ts
@@ -0,0 +1,96 @@
+
+
+/**
+ * PGClient is a client for Postgres database.
+ * Internally client uses go-pg/pg driver.
+ * @example
+ * ```javascript
+ * const postgres = require('nuclei/postgres');
+ * const client = new postgres.PGClient;
+ * ```
+ */
+export class PGClient {
+
+
+ // Constructor of PGClient
+ constructor() {}
+ /**
+ * 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.
+ * @example
+ * ```javascript
+ * const postgres = require('nuclei/postgres');
+ * const isPostgres = postgres.IsPostgres('acme.com', 5432);
+ * ```
+ */
+ public IsPostgres(host: string, port: number): boolean | null {
+ return null;
+ }
+
+
+ /**
+ * 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.
+ * @example
+ * ```javascript
+ * const postgres = require('nuclei/postgres');
+ * const client = new postgres.PGClient;
+ * const connected = client.Connect('acme.com', 5432, 'username', 'password');
+ * ```
+ */
+ public Connect(host: string, port: number, username: string): boolean | null {
+ return null;
+ }
+
+
+ /**
+ * ExecuteQuery connects to Postgres database using given credentials and database name.
+ * and executes a query on the db.
+ * If connection is successful, it returns the result of the query.
+ * @example
+ * ```javascript
+ * const postgres = require('nuclei/postgres');
+ * const client = new postgres.PGClient;
+ * const result = client.ExecuteQuery('acme.com', 5432, 'username', 'password', 'dbname', 'select * from users');
+ * log(to_json(result));
+ * ```
+ */
+ public ExecuteQuery(host: string, port: number, username: string): SQLResult | null | null {
+ return null;
+ }
+
+
+ /**
+ * 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.
+ * @example
+ * ```javascript
+ * const postgres = require('nuclei/postgres');
+ * const client = new postgres.PGClient;
+ * const connected = client.ConnectWithDB('acme.com', 5432, 'username', 'password', 'dbname');
+ * ```
+ */
+ public ConnectWithDB(host: string, port: number, username: string): boolean | null {
+ return null;
+ }
+
+
+}
+
+
+
+/**
+ * SQLResult Interface
+ */
+export interface SQLResult {
+
+ Count?: number,
+
+ Columns?: string[],
+}
+
diff --git a/pkg/js/generated/ts/rdp.ts b/pkg/js/generated/ts/rdp.ts
new file mode 100755
index 0000000000..77dfeafb13
--- /dev/null
+++ b/pkg/js/generated/ts/rdp.ts
@@ -0,0 +1,97 @@
+
+
+/**
+ * CheckRDPAuth checks if the given host and port are running rdp server
+ * with authentication and returns their metadata.
+ * If connection is successful, it returns true.
+ * @example
+ * ```javascript
+ * const rdp = require('nuclei/rdp');
+ * const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
+ * log(toJSON(checkRDPAuth));
+ * ```
+ */
+export function CheckRDPAuth(host: string, port: number): CheckRDPAuthResponse | null {
+ return null;
+}
+
+
+
+/**
+ * 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.
+ * @example
+ * ```javascript
+ * const rdp = require('nuclei/rdp');
+ * const isRDP = rdp.IsRDP('acme.com', 3389);
+ * log(toJSON(isRDP));
+ * ```
+ */
+export function IsRDP(host: string, port: number): IsRDPResponse | null {
+ return null;
+}
+
+
+
+/**
+ * CheckRDPAuthResponse is the response from the CheckRDPAuth function.
+ * this is returned by CheckRDPAuth function.
+ * @example
+ * ```javascript
+ * const rdp = require('nuclei/rdp');
+ * const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
+ * log(toJSON(checkRDPAuth));
+ * ```
+ */
+export interface CheckRDPAuthResponse {
+
+ PluginInfo?: ServiceRDP,
+
+ Auth?: boolean,
+}
+
+
+
+/**
+ * IsRDPResponse is the response from the IsRDP function.
+ * this is returned by IsRDP function.
+ * @example
+ * ```javascript
+ * const rdp = require('nuclei/rdp');
+ * const isRDP = rdp.IsRDP('acme.com', 3389);
+ * log(toJSON(isRDP));
+ * ```
+ */
+export interface IsRDPResponse {
+
+ IsRDP?: boolean,
+
+ OS?: string,
+}
+
+
+
+/**
+ * ServiceRDP Interface
+ */
+export interface ServiceRDP {
+
+ DNSDomainName?: string,
+
+ ForestName?: string,
+
+ OSFingerprint?: string,
+
+ OSVersion?: string,
+
+ TargetName?: string,
+
+ NetBIOSComputerName?: string,
+
+ NetBIOSDomainName?: string,
+
+ DNSComputerName?: string,
+}
+
diff --git a/pkg/js/generated/ts/redis.ts b/pkg/js/generated/ts/redis.ts
new file mode 100755
index 0000000000..fbab318363
--- /dev/null
+++ b/pkg/js/generated/ts/redis.ts
@@ -0,0 +1,70 @@
+
+
+/**
+ * Connect tries to connect redis server with password
+ * @example
+ * ```javascript
+ * const redis = require('nuclei/redis');
+ * const connected = redis.Connect('acme.com', 6379, 'password');
+ * ```
+ */
+export function Connect(host: string, port: number, password: string): boolean | null {
+ return null;
+}
+
+
+
+/**
+ * GetServerInfo returns the server info for a redis server
+ * @example
+ * ```javascript
+ * const redis = require('nuclei/redis');
+ * const info = redis.GetServerInfo('acme.com', 6379);
+ * ```
+ */
+export function GetServerInfo(host: string, port: number): string | null {
+ return null;
+}
+
+
+
+/**
+ * GetServerInfoAuth returns the server info for a redis server
+ * @example
+ * ```javascript
+ * const redis = require('nuclei/redis');
+ * const info = redis.GetServerInfoAuth('acme.com', 6379, 'password');
+ * ```
+ */
+export function GetServerInfoAuth(host: string, port: number, password: string): string | null {
+ return null;
+}
+
+
+
+/**
+ * IsAuthenticated checks if the redis server requires authentication
+ * @example
+ * ```javascript
+ * const redis = require('nuclei/redis');
+ * const isAuthenticated = redis.IsAuthenticated('acme.com', 6379);
+ * ```
+ */
+export function IsAuthenticated(host: string, port: number): boolean | null {
+ return null;
+}
+
+
+
+/**
+ * RunLuaScript runs a lua script on the redis server
+ * @example
+ * ```javascript
+ * const redis = require('nuclei/redis');
+ * const result = redis.RunLuaScript('acme.com', 6379, 'password', 'return redis.call("get", KEYS[1])');
+ * ```
+ */
+export function RunLuaScript(host: string, port: number, password: string, script: string): any | null {
+ return null;
+}
+
diff --git a/pkg/js/generated/ts/rsync.ts b/pkg/js/generated/ts/rsync.ts
new file mode 100755
index 0000000000..afe2146803
--- /dev/null
+++ b/pkg/js/generated/ts/rsync.ts
@@ -0,0 +1,34 @@
+
+
+/**
+ * IsRsync checks if a host is running a Rsync server.
+ * @example
+ * ```javascript
+ * const rsync = require('nuclei/rsync');
+ * const isRsync = rsync.IsRsync('acme.com', 873);
+ * log(toJSON(isRsync));
+ * ```
+ */
+export function IsRsync(host: string, port: number): IsRsyncResponse | null {
+ return null;
+}
+
+
+
+/**
+ * IsRsyncResponse is the response from the IsRsync function.
+ * this is returned by IsRsync function.
+ * @example
+ * ```javascript
+ * const rsync = require('nuclei/rsync');
+ * const isRsync = rsync.IsRsync('acme.com', 873);
+ * log(toJSON(isRsync));
+ * ```
+ */
+export interface IsRsyncResponse {
+
+ IsRsync?: boolean,
+
+ Banner?: string,
+}
+
diff --git a/pkg/js/generated/ts/smb.ts b/pkg/js/generated/ts/smb.ts
new file mode 100755
index 0000000000..b2bcb08f95
--- /dev/null
+++ b/pkg/js/generated/ts/smb.ts
@@ -0,0 +1,236 @@
+
+
+/**
+ * SMBClient is a client for SMB servers.
+ * Internally client uses github.com/zmap/zgrab2/lib/smb/smb driver.
+ * github.com/projectdiscovery/go-smb2 driver
+ * @example
+ * ```javascript
+ * const smb = require('nuclei/smb');
+ * const client = new smb.SMBClient();
+ * ```
+ */
+export class SMBClient {
+
+
+ // Constructor of SMBClient
+ constructor() {}
+ /**
+ * 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
+ * @example
+ * ```javascript
+ * const smb = require('nuclei/smb');
+ * const client = new smb.SMBClient();
+ * const info = client.ConnectSMBInfoMode('acme.com', 445);
+ * log(to_json(info));
+ * ```
+ */
+ public ConnectSMBInfoMode(host: string, port: number): SMBLog | null | null {
+ return null;
+ }
+
+
+ /**
+ * 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
+ * @example
+ * ```javascript
+ * const smb = require('nuclei/smb');
+ * const client = new smb.SMBClient();
+ * const metadata = client.ListSMBv2Metadata('acme.com', 445);
+ * log(to_json(metadata));
+ * ```
+ */
+ public ListSMBv2Metadata(host: string, port: number): ServiceSMB | null | null {
+ return null;
+ }
+
+
+ /**
+ * 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.
+ * @example
+ * ```javascript
+ * const smb = require('nuclei/smb');
+ * const client = new smb.SMBClient();
+ * const shares = client.ListShares('acme.com', 445, 'username', 'password');
+ * for (const share of shares) {
+ * log(share);
+ * }
+ * ```
+ */
+ public ListShares(host: string, port: number, user: string): string[] | null {
+ return null;
+ }
+
+
+ /**
+ * DetectSMBGhost tries to detect SMBGhost vulnerability
+ * by using SMBv3 compression feature.
+ * If the host is vulnerable, it returns true.
+ * @example
+ * ```javascript
+ * const smb = require('nuclei/smb');
+ * const isSMBGhost = smb.DetectSMBGhost('acme.com', 445);
+ * ```
+ */
+ public DetectSMBGhost(host: string, port: number): boolean | null {
+ return null;
+ }
+
+
+}
+
+
+
+/**
+ * HeaderLog Interface
+ */
+export interface HeaderLog {
+
+ ProtocolID?: Uint8Array,
+
+ Status?: number,
+
+ Command?: number,
+
+ Credits?: number,
+
+ Flags?: number,
+}
+
+
+
+/**
+ * NegotiationLog Interface
+ */
+export interface NegotiationLog {
+
+ SecurityMode?: number,
+
+ DialectRevision?: number,
+
+ ServerGuid?: Uint8Array,
+
+ Capabilities?: number,
+
+ SystemTime?: number,
+
+ ServerStartTime?: number,
+
+ AuthenticationTypes?: string[],
+
+ HeaderLog?: HeaderLog,
+}
+
+
+
+/**
+ * SMBCapabilities Interface
+ */
+export interface SMBCapabilities {
+
+ DFSSupport?: boolean,
+
+ Leasing?: boolean,
+
+ LargeMTU?: boolean,
+
+ MultiChan?: boolean,
+
+ Persist?: boolean,
+
+ DirLeasing?: boolean,
+
+ Encryption?: boolean,
+}
+
+
+
+/**
+ * SMBLog Interface
+ */
+export interface SMBLog {
+
+ NativeOs?: string,
+
+ NTLM?: string,
+
+ GroupName?: string,
+
+ HasNTLM?: boolean,
+
+ SupportV1?: boolean,
+
+ Capabilities?: SMBCapabilities,
+
+ NegotiationLog?: NegotiationLog,
+
+ SessionSetupLog?: SessionSetupLog,
+
+ Version?: SMBVersions,
+}
+
+
+
+/**
+ * SMBVersions Interface
+ */
+export interface SMBVersions {
+
+ Major?: number,
+
+ Minor?: number,
+
+ Revision?: number,
+
+ VerString?: string,
+}
+
+
+
+/**
+ * ServiceSMB Interface
+ */
+export interface ServiceSMB {
+
+ SigningEnabled?: boolean,
+
+ SigningRequired?: boolean,
+
+ OSVersion?: string,
+
+ NetBIOSComputerName?: string,
+
+ NetBIOSDomainName?: string,
+
+ DNSComputerName?: string,
+
+ DNSDomainName?: string,
+
+ ForestName?: string,
+}
+
+
+
+/**
+ * SessionSetupLog Interface
+ */
+export interface SessionSetupLog {
+
+ SetupFlags?: number,
+
+ TargetName?: string,
+
+ NegotiateFlags?: number,
+
+ HeaderLog?: HeaderLog,
+}
+
diff --git a/pkg/js/generated/ts/smtp.ts b/pkg/js/generated/ts/smtp.ts
new file mode 100755
index 0000000000..d92544e3db
--- /dev/null
+++ b/pkg/js/generated/ts/smtp.ts
@@ -0,0 +1,198 @@
+
+
+/**
+ * Client is a minimal SMTP client for nuclei scripts.
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const client = new smtp.Client('acme.com', 25);
+ * ```
+ */
+export class Client {
+
+
+ // Constructor of Client
+ constructor(public host: string, public port: string ) {}
+
+
+ /**
+ * IsSMTP checks if a host is running a SMTP server.
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const client = new smtp.Client('acme.com', 25);
+ * const isSMTP = client.IsSMTP();
+ * log(isSMTP)
+ * ```
+ */
+ public IsSMTP(): SMTPResponse | null {
+ return null;
+ }
+
+
+ /**
+ * IsOpenRelay checks if a host is an open relay.
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const message = new smtp.SMTPMessage();
+ * message.From('xyz@projectdiscovery.io');
+ * message.To('xyz2@projectdiscoveyr.io');
+ * message.Subject('hello');
+ * message.Body('hello');
+ * const client = new smtp.Client('acme.com', 25);
+ * const isRelay = client.IsOpenRelay(message);
+ * ```
+ */
+ public IsOpenRelay(msg: SMTPMessage): boolean | null {
+ return null;
+ }
+
+
+ /**
+ * SendMail sends an email using the SMTP protocol.
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const message = new smtp.SMTPMessage();
+ * message.From('xyz@projectdiscovery.io');
+ * message.To('xyz2@projectdiscoveyr.io');
+ * message.Subject('hello');
+ * message.Body('hello');
+ * const client = new smtp.Client('acme.com', 25);
+ * const isSent = client.SendMail(message);
+ * log(isSent)
+ * ```
+ */
+ public SendMail(msg: SMTPMessage): boolean | null {
+ return null;
+ }
+
+
+}
+
+
+
+/**
+ * SMTPMessage is a message to be sent over SMTP
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const message = new smtp.SMTPMessage();
+ * message.From('xyz@projectdiscovery.io');
+ * ```
+ */
+export class SMTPMessage {
+
+
+ // Constructor of SMTPMessage
+ constructor() {}
+ /**
+ * From adds the from field to the message
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const message = new smtp.SMTPMessage();
+ * message.From('xyz@projectdiscovery.io');
+ * ```
+ */
+ public From(email: string): SMTPMessage {
+ return this;
+ }
+
+
+ /**
+ * To adds the to field to the message
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const message = new smtp.SMTPMessage();
+ * message.To('xyz@projectdiscovery.io');
+ * ```
+ */
+ public To(email: string): SMTPMessage {
+ return this;
+ }
+
+
+ /**
+ * Subject adds the subject field to the message
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const message = new smtp.SMTPMessage();
+ * message.Subject('hello');
+ * ```
+ */
+ public Subject(sub: string): SMTPMessage {
+ return this;
+ }
+
+
+ /**
+ * Body adds the message body to the message
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const message = new smtp.SMTPMessage();
+ * message.Body('hello');
+ * ```
+ */
+ public Body(msg: Uint8Array): SMTPMessage {
+ return this;
+ }
+
+
+ /**
+ * Auth when called authenticates using username and password before sending the message
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const message = new smtp.SMTPMessage();
+ * message.Auth('username', 'password');
+ * ```
+ */
+ public Auth(username: string): SMTPMessage {
+ return this;
+ }
+
+
+ /**
+ * String returns the string representation of the message
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const message = new smtp.SMTPMessage();
+ * message.From('xyz@projectdiscovery.io');
+ * message.To('xyz2@projectdiscoveyr.io');
+ * message.Subject('hello');
+ * message.Body('hello');
+ * log(message.String());
+ * ```
+ */
+ public String(): string {
+ return "";
+ }
+
+
+}
+
+
+
+/**
+ * SMTPResponse is the response from the IsSMTP function.
+ * @example
+ * ```javascript
+ * const smtp = require('nuclei/smtp');
+ * const client = new smtp.Client('acme.com', 25);
+ * const isSMTP = client.IsSMTP();
+ * log(isSMTP)
+ * ```
+ */
+export interface SMTPResponse {
+
+ IsSMTP?: boolean,
+
+ Banner?: string,
+}
+
diff --git a/pkg/js/generated/ts/ssh.ts b/pkg/js/generated/ts/ssh.ts
new file mode 100755
index 0000000000..37d04ae670
--- /dev/null
+++ b/pkg/js/generated/ts/ssh.ts
@@ -0,0 +1,230 @@
+
+
+/**
+ * SSHClient is a client for SSH servers.
+ * Internally client uses github.com/zmap/zgrab2/lib/ssh driver.
+ * @example
+ * ```javascript
+ * const ssh = require('nuclei/ssh');
+ * const client = new ssh.SSHClient();
+ * ```
+ */
+export class SSHClient {
+
+
+ // Constructor of SSHClient
+ constructor() {}
+ /**
+ * SetTimeout sets the timeout for the SSH connection in seconds
+ * @example
+ * ```javascript
+ * const ssh = require('nuclei/ssh');
+ * const client = new ssh.SSHClient();
+ * client.SetTimeout(10);
+ * ```
+ */
+ public SetTimeout(sec: number): void {
+ return;
+ }
+
+
+ /**
+ * 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
+ * @example
+ * ```javascript
+ * const ssh = require('nuclei/ssh');
+ * const client = new ssh.SSHClient();
+ * const connected = client.Connect('acme.com', 22, 'username', 'password');
+ * ```
+ */
+ public Connect(host: string, port: number, username: string): boolean | null {
+ return null;
+ }
+
+
+ /**
+ * 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
+ * @example
+ * ```javascript
+ * const ssh = require('nuclei/ssh');
+ * const client = new ssh.SSHClient();
+ * const privateKey = `-----BEGIN RSA PRIVATE KEY----- ...`;
+ * const connected = client.ConnectWithKey('acme.com', 22, 'username', privateKey);
+ * ```
+ */
+ public ConnectWithKey(host: string, port: number, username: string): boolean | null {
+ return null;
+ }
+
+
+ /**
+ * 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
+ * @example
+ * ```javascript
+ * const ssh = require('nuclei/ssh');
+ * const client = new ssh.SSHClient();
+ * const info = client.ConnectSSHInfoMode('acme.com', 22);
+ * log(to_json(info));
+ * ```
+ */
+ public ConnectSSHInfoMode(host: string, port: number): HandshakeLog | null | null {
+ return null;
+ }
+
+
+ /**
+ * Run tries to open a new SSH session, then tries to execute
+ * the provided command in said session
+ * Returns string and error. If error is not nil,
+ * state will be false
+ * The string contains the command output
+ * @example
+ * ```javascript
+ * const ssh = require('nuclei/ssh');
+ * const client = new ssh.SSHClient();
+ * client.Connect('acme.com', 22, 'username', 'password');
+ * const output = client.Run('id');
+ * log(output);
+ * ```
+ */
+ public Run(cmd: string): string | null {
+ return null;
+ }
+
+
+ /**
+ * Close closes the SSH connection and destroys the client
+ * Returns the success state and error. If error is not nil,
+ * state will be false
+ * @example
+ * ```javascript
+ * const ssh = require('nuclei/ssh');
+ * const client = new ssh.SSHClient();
+ * client.Connect('acme.com', 22, 'username', 'password');
+ * const closed = client.Close();
+ * ```
+ */
+ public Close(): boolean | null {
+ return null;
+ }
+
+
+}
+
+
+
+/**
+ * Algorithms Interface
+ */
+export interface Algorithms {
+
+ Kex?: string,
+
+ HostKey?: string,
+
+ W?: DirectionAlgorithms,
+
+ R?: DirectionAlgorithms,
+}
+
+
+
+/**
+ * DirectionAlgorithms Interface
+ */
+export interface DirectionAlgorithms {
+
+ Cipher?: string,
+
+ MAC?: string,
+
+ Compression?: string,
+}
+
+
+
+/**
+ * EndpointId Interface
+ */
+export interface EndpointId {
+
+ Raw?: string,
+
+ ProtoVersion?: string,
+
+ SoftwareVersion?: string,
+
+ Comment?: string,
+}
+
+
+
+/**
+ * HandshakeLog Interface
+ */
+export interface HandshakeLog {
+
+ Banner?: string,
+
+ UserAuth?: string[],
+
+ ServerID?: EndpointId,
+
+ ClientID?: EndpointId,
+
+ ServerKex?: KexInitMsg,
+
+ ClientKex?: KexInitMsg,
+
+ AlgorithmSelection?: Algorithms,
+}
+
+
+
+/**
+ * KexInitMsg Interface
+ */
+export interface KexInitMsg {
+
+ CiphersServerClient?: string[],
+
+ MACsClientServer?: string[],
+
+ MACsServerClient?: string[],
+
+ LanguagesClientServer?: string[],
+
+ KexAlgos?: string[],
+
+ CiphersClientServer?: string[],
+
+ Reserved?: number,
+
+ CompressionClientServer?: string[],
+
+ CompressionServerClient?: string[],
+
+ LanguagesServerClient?: string[],
+
+ FirstKexFollows?: boolean,
+
+ /**
+ * fixed size array of length: [16]
+ */
+
+ Cookie?: Uint8Array,
+
+ ServerHostKeyAlgos?: string[],
+}
+
diff --git a/pkg/js/generated/ts/structs.ts b/pkg/js/generated/ts/structs.ts
new file mode 100755
index 0000000000..5c00f5ea86
--- /dev/null
+++ b/pkg/js/generated/ts/structs.ts
@@ -0,0 +1,49 @@
+
+
+/**
+ * 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)
+ * @example
+ * ```javascript
+ * const structs = require('nuclei/structs');
+ * const packed = structs.Pack('H', [0]);
+ * ```
+ */
+export function Pack(formatStr: string, msg: any): Uint8Array | null {
+ return null;
+}
+
+
+
+/**
+ * StructsCalcSize returns the number of bytes needed to pack the values according to the given format.
+ * Ex: structs.CalcSize("H")
+ * @example
+ * ```javascript
+ * const structs = require('nuclei/structs');
+ * const size = structs.CalcSize('H');
+ * ```
+ */
+export function StructsCalcSize(format: string): number | null {
+ return null;
+}
+
+
+
+/**
+ * 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])
+ * @example
+ * ```javascript
+ * const structs = require('nuclei/structs');
+ * const result = structs.Unpack('H', [0]);
+ * ```
+ */
+export function Unpack(format: string, msg: Uint8Array): any | null {
+ return null;
+}
+
diff --git a/pkg/js/generated/ts/telnet.ts b/pkg/js/generated/ts/telnet.ts
new file mode 100755
index 0000000000..cd49c2078e
--- /dev/null
+++ b/pkg/js/generated/ts/telnet.ts
@@ -0,0 +1,34 @@
+
+
+/**
+ * IsTelnet checks if a host is running a Telnet server.
+ * @example
+ * ```javascript
+ * const telnet = require('nuclei/telnet');
+ * const isTelnet = telnet.IsTelnet('acme.com', 23);
+ * log(toJSON(isTelnet));
+ * ```
+ */
+export function IsTelnet(host: string, port: number): IsTelnetResponse | null {
+ return null;
+}
+
+
+
+/**
+ * IsTelnetResponse is the response from the IsTelnet function.
+ * this is returned by IsTelnet function.
+ * @example
+ * ```javascript
+ * const telnet = require('nuclei/telnet');
+ * const isTelnet = telnet.IsTelnet('acme.com', 23);
+ * log(toJSON(isTelnet));
+ * ```
+ */
+export interface IsTelnetResponse {
+
+ IsTelnet?: boolean,
+
+ Banner?: string,
+}
+
diff --git a/pkg/js/generated/ts/vnc.ts b/pkg/js/generated/ts/vnc.ts
new file mode 100755
index 0000000000..870151e2bc
--- /dev/null
+++ b/pkg/js/generated/ts/vnc.ts
@@ -0,0 +1,35 @@
+
+
+/**
+ * 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.
+ * @example
+ * ```javascript
+ * const vnc = require('nuclei/vnc');
+ * const isVNC = vnc.IsVNC('acme.com', 5900);
+ * log(toJSON(isVNC));
+ * ```
+ */
+export function IsVNC(host: string, port: number): IsVNCResponse | null {
+ return null;
+}
+
+
+
+/**
+ * IsVNCResponse is the response from the IsVNC function.
+ * @example
+ * ```javascript
+ * const vnc = require('nuclei/vnc');
+ * const isVNC = vnc.IsVNC('acme.com', 5900);
+ * log(toJSON(isVNC));
+ * ```
+ */
+export interface IsVNCResponse {
+
+ IsVNC?: boolean,
+
+ Banner?: string,
+}
+
diff --git a/pkg/js/gojs/gojs.go b/pkg/js/gojs/gojs.go
index 0b47cbd1de..3b43fe13fb 100644
--- a/pkg/js/gojs/gojs.go
+++ b/pkg/js/gojs/gojs.go
@@ -5,6 +5,7 @@ import (
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/require"
+ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
)
type Objects map[string]interface{}
@@ -75,3 +76,10 @@ func (p *GojaModule) Register() Module {
return p
}
+
+// GetClassConstructor returns a constructor for any given go struct type for goja runtime
+func GetClassConstructor[T any](instance *T) func(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
+ return func(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
+ return utils.LinkConstructor[*T](call, runtime, instance)
+ }
+}
diff --git a/pkg/js/libs/bytes/buffer.go b/pkg/js/libs/bytes/buffer.go
index 3a3058bf84..e384741826 100644
--- a/pkg/js/libs/bytes/buffer.go
+++ b/pkg/js/libs/bytes/buffer.go
@@ -5,71 +5,132 @@ import (
"github.com/dop251/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/structs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
)
-// 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
-}
+type (
+ // Buffer is a bytes/Uint8Array type in javascript
+ // @example
+ // ```javascript
+ // const bytes = require('nuclei/bytes');
+ // const bytes = new bytes.Buffer();
+ // ```
+ // @example
+ // ```javascript
+ // const bytes = require('nuclei/bytes');
+ // // optionally it can accept existing byte/Uint8Array as input
+ // const bytes = new bytes.Buffer([1, 2, 3]);
+ // ```
+ 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,
- "WriteString": obj.WriteString,
- "Pack": obj.Pack,
- "Bytes": obj.Bytes,
- "String": obj.String,
- "Len": obj.Len,
- "Hex": obj.Hex,
- "Hexdump": obj.Hexdump,
+func NewBuffer(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
+ if len(call.Arguments) > 0 {
+ if arg, ok := call.Argument(0).Export().([]byte); ok {
+ return utils.LinkConstructor(call, runtime, &Buffer{buf: arg})
+ } else {
+ utils.NewNucleiJS(runtime).Throw("Invalid argument type. Expected bytes/Uint8Array as input but got %T", call.Argument(0).Export())
+ }
}
+ return utils.LinkConstructor(call, runtime, &Buffer{})
}
-// Write appends a byte slice to the buffer.
+// Write appends the given data to the buffer.
+// @example
+// ```javascript
+// const bytes = require('nuclei/bytes');
+// const buffer = new bytes.Buffer();
+// buffer.Write([1, 2, 3]);
+// ```
func (b *Buffer) Write(data []byte) *Buffer {
b.buf = append(b.buf, data...)
return b
}
-// WriteString appends a string to the buffer.
+// WriteString appends the given string data to the buffer.
+// @example
+// ```javascript
+// const bytes = require('nuclei/bytes');
+// const buffer = new bytes.Buffer();
+// buffer.WriteString('hello');
+// ```
func (b *Buffer) WriteString(data string) *Buffer {
b.buf = append(b.buf, []byte(data)...)
return b
}
-// Bytes returns the byte slice of the buffer.
+// Bytes returns the byte representation of the buffer.
+// @example
+// ```javascript
+// const bytes = require('nuclei/bytes');
+// const buffer = new bytes.Buffer();
+// buffer.WriteString('hello');
+// log(buffer.Bytes());
+// ```
func (b *Buffer) Bytes() []byte {
return b.buf
}
// String returns the string representation of the buffer.
+// @example
+// ```javascript
+// const bytes = require('nuclei/bytes');
+// const buffer = new bytes.Buffer();
+// buffer.WriteString('hello');
+// log(buffer.String());
+// ```
func (b *Buffer) String() string {
return string(b.buf)
}
// Len returns the length of the buffer.
+// @example
+// ```javascript
+// const bytes = require('nuclei/bytes');
+// const buffer = new bytes.Buffer();
+// buffer.WriteString('hello');
+// log(buffer.Len());
+// ```
func (b *Buffer) Len() int {
return len(b.buf)
}
// Hex returns the hex representation of the buffer.
+// @example
+// ```javascript
+// const bytes = require('nuclei/bytes');
+// const buffer = new bytes.Buffer();
+// buffer.WriteString('hello');
+// log(buffer.Hex());
+// ```
func (b *Buffer) Hex() string {
return hex.EncodeToString(b.buf)
}
// Hexdump returns the hexdump representation of the buffer.
+// @example
+// ```javascript
+// const bytes = require('nuclei/bytes');
+// const buffer = new bytes.Buffer();
+// buffer.WriteString('hello');
+// log(buffer.Hexdump());
+// ```
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 {
+// @example
+// ```javascript
+// const bytes = require('nuclei/bytes');
+// const buffer = new bytes.Buffer();
+// buffer.Pack('I', 123);
+// ```
+func (b *Buffer) Pack(formatStr string, msg any) error {
bin, err := structs.Pack(formatStr, msg)
if err != nil {
return err
diff --git a/pkg/js/libs/fs/fs.go b/pkg/js/libs/fs/fs.go
index 5dcbb03812..e3a3fd7bdd 100644
--- a/pkg/js/libs/fs/fs.go
+++ b/pkg/js/libs/fs/fs.go
@@ -6,9 +6,27 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
-// ListDir lists all files and directories within a path
+// ListDir lists itemType values within a directory
// depending on the itemType provided
-// itemType can be any one of ['file','dir','all']
+// itemType can be any one of ['file','dir',”]
+// @example
+// ```javascript
+// const fs = require('nuclei/fs');
+// // this will only return files in /tmp directory
+// const files = fs.ListDir('/tmp', 'file');
+// ```
+// @example
+// ```javascript
+// const fs = require('nuclei/fs');
+// // this will only return directories in /tmp directory
+// const dirs = fs.ListDir('/tmp', 'dir');
+// ```
+// @example
+// ```javascript
+// const fs = require('nuclei/fs');
+// // when no itemType is provided, it will return both files and directories
+// const items = fs.ListDir('/tmp');
+// ```
func ListDir(path string, itemType string) ([]string, error) {
finalPath, err := protocolstate.NormalizePath(path)
if err != nil {
@@ -32,6 +50,13 @@ func ListDir(path string, itemType string) ([]string, error) {
}
// ReadFile reads file contents within permitted paths
+// and returns content as byte array
+// @example
+// ```javascript
+// const fs = require('nuclei/fs');
+// // here permitted directories are $HOME/nuclei-templates/*
+// const content = fs.ReadFile('helpers/usernames.txt');
+// ```
func ReadFile(path string) ([]byte, error) {
finalPath, err := protocolstate.NormalizePath(path)
if err != nil {
@@ -43,6 +68,12 @@ func ReadFile(path string) ([]byte, error) {
// ReadFileAsString reads file contents within permitted paths
// and returns content as string
+// @example
+// ```javascript
+// const fs = require('nuclei/fs');
+// // here permitted directories are $HOME/nuclei-templates/*
+// const content = fs.ReadFileAsString('helpers/usernames.txt');
+// ```
func ReadFileAsString(path string) (string, error) {
bin, err := ReadFile(path)
if err != nil {
@@ -52,7 +83,14 @@ func ReadFileAsString(path string) (string, error) {
}
// ReadFilesFromDir reads all files from a directory
-// and returns a array with file contents of all files
+// and returns a string array with file contents of all files
+// @example
+// ```javascript
+// const fs = require('nuclei/fs');
+// // here permitted directories are $HOME/nuclei-templates/*
+// const contents = fs.ReadFilesFromDir('helpers/ssh-keys');
+// log(contents);
+// ```
func ReadFilesFromDir(dir string) ([]string, error) {
files, err := ListDir(dir, "file")
if err != nil {
diff --git a/pkg/js/libs/ikev2/ikev2.go b/pkg/js/libs/ikev2/ikev2.go
index c41fedf2f7..37e013c91a 100644
--- a/pkg/js/libs/ikev2/ikev2.go
+++ b/pkg/js/libs/ikev2/ikev2.go
@@ -1,6 +1,7 @@
package ikev2
import (
+ "fmt"
"io"
"github.com/projectdiscovery/n3iwf/pkg/ike/message"
@@ -11,31 +12,91 @@ 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
+type (
+ // 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.
+ IKEMessage struct {
+ InitiatorSPI uint64
+ Version uint8
+ ExchangeType uint8
+ Flags uint8
+ payloads []IKEPayload
+ }
+)
+
+// AppendPayload appends a payload to the IKE message
+// payload can be any of the payloads like IKENotification, IKENonce, etc.
+// @example
+// ```javascript
+// const ikev2 = require('nuclei/ikev2');
+// const message = new ikev2.IKEMessage();
+// const nonce = new ikev2.IKENonce();
+// nonce.NonceData = [1, 2, 3];
+// message.AppendPayload(nonce);
+// ```
+func (m *IKEMessage) AppendPayload(payload any) error {
+ if _, ok := payload.(IKEPayload); !ok {
+ return fmt.Errorf("invalid payload type only types defined in ikev module like IKENotification, IKENonce, etc. are allowed")
+ }
+ m.payloads = append(m.payloads, payload.(IKEPayload))
+ return nil
+}
+
+// Encode encodes the final IKE message
+// @example
+// ```javascript
+// const ikev2 = require('nuclei/ikev2');
+// const message = new ikev2.IKEMessage();
+// const nonce = new ikev2.IKENonce();
+// nonce.NonceData = [1, 2, 3];
+// message.AppendPayload(nonce);
+// log(message.Encode());
+// ```
+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
}
// 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
-}
+type (
+ // IKEv2Notify is the IKEv2 Notification payload
+ // this implements the IKEPayload interface
+ // @example
+ // ```javascript
+ // const ikev2 = require('nuclei/ikev2');
+ // const notify = new ikev2.IKENotification();
+ // notify.NotifyMessageType = ikev2.IKE_NOTIFY_NO_PROPOSAL_CHOSEN;
+ // notify.NotificationData = [1, 2, 3];
+ // ```
+ IKENotification struct {
+ NotifyMessageType uint16
+ NotificationData []byte
+ }
+)
// encode encodes the IKEv2 Notification payload
func (i *IKENotification) encode() (message.IKEPayload, error) {
@@ -63,10 +124,19 @@ const (
IKE_FLAGS_InitiatorBitCheck = 0x08
)
-// IKENonce is the IKEv2 Nonce payload
-type IKENonce struct {
- NonceData []byte
-}
+type (
+ // IKENonce is the IKEv2 Nonce payload
+ // this implements the IKEPayload interface
+ // @example
+ // ```javascript
+ // const ikev2 = require('nuclei/ikev2');
+ // const nonce = new ikev2.IKENonce();
+ // nonce.NonceData = [1, 2, 3];
+ // ```
+ IKENonce struct {
+ NonceData []byte
+ }
+)
// encode encodes the IKEv2 Nonce payload
func (i *IKENonce) encode() (message.IKEPayload, error) {
@@ -75,30 +145,3 @@ func (i *IKENonce) encode() (message.IKEPayload, error) {
}
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/pkg/js/libs/kerberos/kerberos.go b/pkg/js/libs/kerberos/kerberos.go
deleted file mode 100644
index 954dd0ceb6..0000000000
--- a/pkg/js/libs/kerberos/kerberos.go
+++ /dev/null
@@ -1,193 +0,0 @@
-package kerberos
-
-import (
- "encoding/hex"
- "fmt"
- "html/template"
- "strings"
-
- "github.com/projectdiscovery/nuclei/v3/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))
- defer cl.Destroy()
-
- 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
-}
-
-type TGS struct {
- Ticket messages.Ticket
- Hash string
-}
-
-func (c *KerberosClient) GetServiceTicket(domain, controller string, username, password string, target, spn string) (TGS, error) {
- var tgs TGS
-
- if !protocolstate.IsHostAllowed(domain) {
- // host is not valid according to network policy
- return tgs, protocolstate.ErrHostDenied.Msgf(domain)
- }
-
- opts, err := newKerbrosEnumUserOpts(domain, controller)
- if err != nil {
- return tgs, err
- }
- cl := kclient.NewWithPassword(username, opts.realm, password, opts.config, kclient.DisablePAFXFAST(true))
- defer cl.Destroy()
-
- ticket, _, err := cl.GetServiceTicket(spn)
- if err != nil {
- return tgs, err
- }
-
- hashcat, err := tgsToHashcat(ticket, target)
- if err != nil {
- return tgs, err
- }
-
- return TGS{
- Ticket: ticket,
- Hash: hashcat,
- }, nil
-}
-
-func tgsToHashcat(tgs messages.Ticket, username string) (string, error) {
- return fmt.Sprintf("$krb5tgs$%d$*%s$%s$%s*$%s$%s",
- tgs.EncPart.EType,
- username,
- tgs.Realm,
- strings.Join(tgs.SName.NameString[:], "/"),
- hex.EncodeToString(tgs.EncPart.Cipher[:16]),
- hex.EncodeToString(tgs.EncPart.Cipher[16:]),
- ), nil
-}
diff --git a/pkg/js/libs/kerberos/kerberosx.go b/pkg/js/libs/kerberos/kerberosx.go
new file mode 100644
index 0000000000..ea3e5921d5
--- /dev/null
+++ b/pkg/js/libs/kerberos/kerberosx.go
@@ -0,0 +1,356 @@
+package kerberos
+
+import (
+ "strings"
+
+ "github.com/dop251/goja"
+ kclient "github.com/jcmturner/gokrb5/v8/client"
+ kconfig "github.com/jcmturner/gokrb5/v8/config"
+ "github.com/jcmturner/gokrb5/v8/iana/errorcode"
+ "github.com/jcmturner/gokrb5/v8/messages"
+ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+ ConversionUtil "github.com/projectdiscovery/utils/conversion"
+)
+
+type (
+ // EnumerateUserResponse is the response from EnumerateUser
+ EnumerateUserResponse struct {
+ Valid bool `json:"valid"`
+ ASREPHash string `json:"asrep_hash"`
+ Error string `json:"error"`
+ }
+)
+
+type (
+ // TGS is the response from GetServiceTicket
+ TGS struct {
+ Ticket messages.Ticket `json:"ticket"`
+ Hash string `json:"hash"`
+ ErrMsg string `json:"error"`
+ }
+)
+
+type (
+ // Config is extra configuration for the kerberos client
+ Config struct {
+ ip string
+ timeout int // in seconds
+ }
+)
+
+// SetIPAddress sets the IP address for the kerberos client
+// @example
+// ```javascript
+// const kerberos = require('nuclei/kerberos');
+// const cfg = new kerberos.Config();
+// cfg.SetIPAddress('10.10.10.1');
+// ```
+func (c *Config) SetIPAddress(ip string) *Config {
+ c.ip = ip
+ return c
+}
+
+// SetTimeout sets the RW timeout for the kerberos client
+// @example
+// ```javascript
+// const kerberos = require('nuclei/kerberos');
+// const cfg = new kerberos.Config();
+// cfg.SetTimeout(5);
+// ```
+func (c *Config) SetTimeout(timeout int) *Config {
+ c.timeout = timeout
+ return c
+}
+
+// Example Values for jargons
+// Realm: ACME.COM (Authentical zone / security area)
+// Domain: acme.com (Public website / domain)
+// DomainController: dc.acme.com (Domain Controller / Active Directory Server)
+// KDC: kdc.acme.com (Key Distribution Center / Authentication Server)
+
+type (
+ // Known Issues:
+ // Hardcoded timeout in gokrb5 library
+ // TGT / Session Handling not exposed
+ // Client is kerberos client
+ // @example
+ // ```javascript
+ // const kerberos = require('nuclei/kerberos');
+ // // if controller is empty a dns lookup for default kdc server will be performed
+ // const client = new kerberos.Client('acme.com', 'kdc.acme.com');
+ // ```
+ Client struct {
+ nj *utils.NucleiJS // helper functions/bindings
+ Krb5Config *kconfig.Config
+ Realm string
+ config Config
+ }
+)
+
+// Constructor for Kerberos Client
+// Constructor: constructor(public domain: string, public controller?: string)
+// When controller is empty or not given krb5 will perform a DNS lookup for the default KDC server
+// and retrieve its address from the DNS server
+func NewKerberosClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
+ // setup nucleijs utils
+ c := &Client{nj: utils.NewNucleiJS(runtime)}
+ c.nj.ObjectSig = "Client(domain, {controller})" // will be included in error messages
+
+ // get arguments (type assertion is efficient than reflection)
+ // when accepting type as input like net.Conn we can use utils.GetArg
+ domain, _ := c.nj.GetArg(call.Arguments, 0).(string)
+ controller, _ := c.nj.GetArg(call.Arguments, 1).(string)
+
+ // validate arguments
+ c.nj.Require(domain != "", "domain cannot be empty")
+
+ cfg := kconfig.New()
+
+ if controller != "" {
+ // validate controller hostport
+ if !protocolstate.IsHostAllowed(controller) {
+ c.nj.Throw("domain controller address blacklisted by network policy")
+ }
+
+ tmp := strings.Split(controller, ":")
+ if len(tmp) == 1 {
+ tmp = append(tmp, "88")
+ }
+ realm := strings.ToUpper(domain)
+ cfg.LibDefaults.DefaultRealm = realm // set default realm
+ cfg.Realms = []kconfig.Realm{
+ {
+ Realm: realm,
+ KDC: []string{tmp[0] + ":" + tmp[1]},
+ AdminServer: []string{tmp[0] + ":" + tmp[1]},
+ KPasswdServer: []string{tmp[0] + ":464"}, // default password server port
+ },
+ }
+ cfg.DomainRealm = make(kconfig.DomainRealm)
+ } else {
+ // if controller is empty use DNS lookup
+ cfg.LibDefaults.DNSLookupKDC = true
+ cfg.LibDefaults.DefaultRealm = strings.ToUpper(domain)
+ cfg.DomainRealm = make(kconfig.DomainRealm)
+ }
+ c.Krb5Config = cfg
+ c.Realm = strings.ToUpper(domain)
+
+ // Link Constructor to Client and return
+ return utils.LinkConstructor(call, runtime, c)
+}
+
+// NewKerberosClientFromString creates a new kerberos client from a string
+// by parsing krb5.conf
+// @example
+// ```javascript
+// const kerberos = require('nuclei/kerberos');
+// const client = kerberos.NewKerberosClientFromString(`
+// [libdefaults]
+// default_realm = ACME.COM
+// dns_lookup_kdc = true
+// `);
+// ```
+func NewKerberosClientFromString(cfg string) (*Client, error) {
+ config, err := kconfig.NewFromString(cfg)
+ if err != nil {
+ return nil, err
+ }
+ return &Client{Krb5Config: config}, nil
+}
+
+// SetConfig sets additional config for the kerberos client
+// Note: as of now ip and timeout overrides are only supported
+// in EnumerateUser due to fastdialer but can be extended to other methods currently
+// @example
+// ```javascript
+// const kerberos = require('nuclei/kerberos');
+// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
+// const cfg = new kerberos.Config();
+// cfg.SetIPAddress('192.168.100.22');
+// cfg.SetTimeout(5);
+// client.SetConfig(cfg);
+// ```
+func (c *Client) SetConfig(cfg *Config) {
+ if cfg == nil {
+ c.nj.Throw("config cannot be nil")
+ }
+ c.config = *cfg
+}
+
+// EnumerateUser and attempt to get AS-REP hash by disabling PA-FX-FAST
+// @example
+// ```javascript
+// const kerberos = require('nuclei/kerberos');
+// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
+// const resp = client.EnumerateUser('pdtm');
+// log(resp);
+// ```
+func (c *Client) EnumerateUser(username string) (EnumerateUserResponse, error) {
+ c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")
+ password := "password"
+ // client does not actually attempt connection it manages state here
+ client := kclient.NewWithPassword(username, c.Realm, password, c.Krb5Config, kclient.DisablePAFXFAST(true))
+ defer client.Destroy()
+
+ // generate ASReq hash
+ req, err := messages.NewASReqForTGT(client.Credentials.Domain(), client.Config, client.Credentials.CName())
+ c.nj.HandleError(err, "failed to generate TGT request")
+
+ // marshal request
+ b, err := req.Marshal()
+ c.nj.HandleError(err, "failed to marshal TGT request")
+
+ data, err := SendToKDC(c, string(b))
+ rb := ConversionUtil.Bytes(data)
+
+ if err == nil {
+ var ASRep messages.ASRep
+ resp := EnumerateUserResponse{Valid: true}
+ err = ASRep.Unmarshal(rb)
+ if err != nil {
+ resp.Error = err.Error()
+ return resp, nil
+ }
+ hashcatString, _ := ASRepToHashcat(ASRep)
+ resp.ASREPHash = hashcatString
+ return resp, nil
+ }
+
+ resp := EnumerateUserResponse{}
+ e, ok := err.(messages.KRBError)
+ if !ok {
+ return resp, err
+ }
+ if e.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {
+ resp.Valid = true
+ resp.Error = errorcode.Lookup(e.ErrorCode)
+ return resp, nil
+ }
+ resp.Error = errorcode.Lookup(e.ErrorCode)
+ return resp, nil
+}
+
+// GetServiceTicket returns a TGS for a given user, password and SPN
+// @example
+// ```javascript
+// const kerberos = require('nuclei/kerberos');
+// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
+// const resp = client.GetServiceTicket('pdtm', 'password', 'HOST/CLIENT1');
+// log(resp);
+// ```
+func (c *Client) GetServiceTicket(User, Pass, SPN string) (TGS, error) {
+ c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")
+ c.nj.Require(User != "", "User cannot be empty")
+ c.nj.Require(Pass != "", "Pass cannot be empty")
+ c.nj.Require(SPN != "", "SPN cannot be empty")
+
+ if len(c.Krb5Config.Realms) > 0 {
+ // this means dc address was given
+ for _, r := range c.Krb5Config.Realms {
+ for _, kdc := range r.KDC {
+ if !protocolstate.IsHostAllowed(kdc) {
+ c.nj.Throw("KDC address %v blacklisted by network policy", kdc)
+ }
+ }
+ for _, kpasswd := range r.KPasswdServer {
+ if !protocolstate.IsHostAllowed(kpasswd) {
+ c.nj.Throw("Kpasswd address %v blacklisted by network policy", kpasswd)
+ }
+ }
+ }
+ } else {
+ // here net.Dialer is used instead of fastdialer hence get possible addresses
+ // and check if they are allowed by network policy
+ _, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true)
+ for _, v := range kdcs {
+ if !protocolstate.IsHostAllowed(v) {
+ c.nj.Throw("KDC address %v blacklisted by network policy", v)
+ }
+ }
+ }
+
+ // client does not actually attempt connection it manages state here
+ client := kclient.NewWithPassword(User, c.Realm, Pass, c.Krb5Config, kclient.DisablePAFXFAST(true))
+ defer client.Destroy()
+
+ resp := TGS{}
+
+ ticket, _, err := client.GetServiceTicket(SPN)
+ resp.Ticket = ticket
+ if err != nil {
+ if code, ok := err.(messages.KRBError); ok {
+ resp.ErrMsg = errorcode.Lookup(code.ErrorCode)
+ return resp, err
+ }
+ return resp, err
+ }
+ // convert AS-REP to hashcat format
+ hashcat, err := TGStoHashcat(ticket, c.Realm)
+ if err != nil {
+ if code, ok := err.(messages.KRBError); ok {
+ resp.ErrMsg = errorcode.Lookup(code.ErrorCode)
+ return resp, err
+ }
+ return resp, err
+ }
+ resp.Ticket = ticket
+ resp.Hash = hashcat
+ return resp, nil
+}
+
+// // GetASREP returns AS-REP for a given user and password
+// // it contains Client's TGT , Principal and Session Key
+// // Signature: GetASREP(User, Pass)
+// // @param User: string
+// // @param Pass: string
+// func (c *Client) GetASREP(User, Pass string) messages.ASRep {
+// c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")
+// c.nj.Require(User != "", "User cannot be empty")
+// c.nj.Require(Pass != "", "Pass cannot be empty")
+
+// if len(c.Krb5Config.Realms) > 0 {
+// // this means dc address was given
+// for _, r := range c.Krb5Config.Realms {
+// for _, kdc := range r.KDC {
+// if !protocolstate.IsHostAllowed(kdc) {
+// c.nj.Throw("KDC address blacklisted by network policy")
+// }
+// }
+// for _, kpasswd := range r.KPasswdServer {
+// if !protocolstate.IsHostAllowed(kpasswd) {
+// c.nj.Throw("Kpasswd address blacklisted by network policy")
+// }
+// }
+// }
+// } else {
+// // here net.Dialer is used instead of fastdialer hence get possible addresses
+// // and check if they are allowed by network policy
+// _, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true)
+// for _, v := range kdcs {
+// if !protocolstate.IsHostAllowed(v) {
+// c.nj.Throw("KDC address blacklisted by network policy")
+// }
+// }
+// }
+
+// // login to get TGT
+// cl := kclient.NewWithPassword(User, c.Realm, Pass, c.Krb5Config, kclient.DisablePAFXFAST(true))
+// defer cl.Destroy()
+
+// // generate ASReq
+// ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
+// c.nj.HandleError(err, "failed to generate TGT request")
+
+// // exchange AS-REQ for AS-REP
+// resp, err := cl.ASExchange(c.Realm, ASReq, 0)
+// c.nj.HandleError(err, "failed to exchange AS-REQ")
+
+// // try to decrypt encrypted parts of the response and TGT
+// key, err := resp.DecryptEncPart(cl.Credentials)
+// if err == nil {
+// _ = resp.Ticket.Decrypt(key)
+// }
+// return resp
+// }
diff --git a/pkg/js/libs/kerberos/sendtokdc.go b/pkg/js/libs/kerberos/sendtokdc.go
new file mode 100644
index 0000000000..7e14386a7d
--- /dev/null
+++ b/pkg/js/libs/kerberos/sendtokdc.go
@@ -0,0 +1,213 @@
+package kerberos
+
+// the following code is adapted from the original library
+// https://github.com/jcmturner/gokrb5/blob/855dbc707a37a21467aef6c0245fcf3328dc39ed/v8/client/network.go
+// it is copied here because the library does not export "SendToKDC()"
+
+import (
+ "context"
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "net"
+ "strings"
+ "time"
+
+ "github.com/jcmturner/gokrb5/v8/messages"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+// sendtokdc.go deals with actual sending and receiving responses from KDC
+// SendToKDC sends a message to the KDC and returns the response.
+// It first tries to send the message over TCP, and if that fails, it falls back to UDP.(and vice versa)
+// @example
+// ```javascript
+// const kerberos = require('nuclei/kerberos');
+// const client = new kerberos.Client('acme.com');
+// const response = kerberos.SendToKDC(client, 'message');
+// ```
+func SendToKDC(kclient *Client, msg string) (string, error) {
+ if kclient == nil || kclient.nj == nil || kclient.Krb5Config == nil || kclient.Realm == "" {
+ return "", fmt.Errorf("kerberos client is not initialized")
+ }
+ if kclient.config.timeout == 0 {
+ kclient.config.timeout = 5 // default timeout 5 seconds
+ }
+ var response []byte
+ var err error
+
+ response, err = sendToKDCTcp(kclient, msg)
+ if err == nil {
+ // if it related to tcp
+ bin, err := CheckKrbError(response)
+ if err == nil {
+ return string(bin), nil
+ }
+ // if it is krb error no need to do udp
+ if e, ok := err.(messages.KRBError); ok {
+ return string(response), e
+ }
+ }
+
+ // fallback to udp
+ response, err = sendToKDCUdp(kclient, msg)
+ if err == nil {
+ // if it related to udp
+ bin, err := CheckKrbError(response)
+ if err == nil {
+ return string(bin), nil
+ }
+ }
+ return string(response), err
+}
+
+// sendToKDCTcp sends a message to the KDC via TCP.
+func sendToKDCTcp(kclient *Client, msg string) ([]byte, error) {
+ _, kdcs, err := kclient.Krb5Config.GetKDCs(kclient.Realm, true)
+ kclient.nj.HandleError(err, "error getting KDCs")
+ kclient.nj.Require(len(kdcs) > 0, "no KDCs found")
+
+ var errs []string
+ for i := 1; i <= len(kdcs); i++ {
+ host, port, err := net.SplitHostPort(kdcs[i])
+ if err == nil && kclient.config.ip != "" {
+ // use that ip address instead of realm/domain for resolving
+ host = kclient.config.ip
+ }
+ tcpConn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, port))
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error establishing connection to %s: %v", kdcs[i], err))
+ continue
+ }
+ defer tcpConn.Close()
+ _ = tcpConn.SetDeadline(time.Now().Add(time.Duration(kclient.config.timeout) * time.Second)) //read and write deadline
+ rb, err := sendTCP(tcpConn.(*net.TCPConn), []byte(msg))
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error sending to %s: %v", kdcs[i], err))
+ continue
+ }
+ return rb, nil
+ }
+ if len(errs) > 0 {
+ return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; "))
+ }
+ return nil, nil
+}
+
+// sendToKDCUdp sends a message to the KDC via UDP.
+func sendToKDCUdp(kclient *Client, msg string) ([]byte, error) {
+ _, kdcs, err := kclient.Krb5Config.GetKDCs(kclient.Realm, true)
+ kclient.nj.HandleError(err, "error getting KDCs")
+ kclient.nj.Require(len(kdcs) > 0, "no KDCs found")
+
+ var errs []string
+ for i := 1; i <= len(kdcs); i++ {
+ host, port, err := net.SplitHostPort(kdcs[i])
+ if err == nil && kclient.config.ip != "" {
+ // use that ip address instead of realm/domain for resolving
+ host = kclient.config.ip
+ }
+ udpConn, err := protocolstate.Dialer.Dial(context.TODO(), "udp", net.JoinHostPort(host, port))
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error establishing connection to %s: %v", kdcs[i], err))
+ continue
+ }
+ defer udpConn.Close()
+ _ = udpConn.SetDeadline(time.Now().Add(time.Duration(kclient.config.timeout) * time.Second)) //read and write deadline
+ rb, err := sendUDP(udpConn.(*net.UDPConn), []byte(msg))
+ if err != nil {
+ errs = append(errs, fmt.Sprintf("error sending to %s: %v", kdcs[i], err))
+ continue
+ }
+ return rb, nil
+ }
+ if len(errs) > 0 {
+ // fallback to tcp
+ return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; "))
+ }
+ return nil, nil
+}
+
+// sendUDP sends bytes to connection over UDP.
+func sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
+ var r []byte
+ defer conn.Close()
+ _, err := conn.Write(b)
+ if err != nil {
+ return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err)
+ }
+ udpbuf := make([]byte, 4096)
+ n, _, err := conn.ReadFrom(udpbuf)
+ r = udpbuf[:n]
+ if err != nil {
+ return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err)
+ }
+ if len(r) < 1 {
+ return r, fmt.Errorf("no response data from %s", conn.RemoteAddr().String())
+ }
+ return r, nil
+}
+
+// sendTCP sends bytes to connection over TCP.
+func sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
+ defer conn.Close()
+ var r []byte
+ // RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order.
+ hb := make([]byte, 4)
+ binary.BigEndian.PutUint32(hb, uint32(len(b)))
+ b = append(hb, b...)
+
+ _, err := conn.Write(b)
+ if err != nil {
+ return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
+ }
+
+ sh := make([]byte, 4)
+ _, err = conn.Read(sh)
+ if err != nil {
+ return r, fmt.Errorf("error reading response size header: %v", err)
+ }
+ s := binary.BigEndian.Uint32(sh)
+
+ rb := make([]byte, s)
+ _, err = io.ReadFull(conn, rb)
+ if err != nil {
+ return r, fmt.Errorf("error reading response: %v", err)
+ }
+ if len(rb) < 1 {
+ return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
+ }
+ return rb, nil
+}
+
+// CheckKrbError checks if the response bytes from the KDC are a KRBError.
+func CheckKrbError(b []byte) ([]byte, error) {
+ var KRBErr messages.KRBError
+ if err := KRBErr.Unmarshal(b); err == nil {
+ return b, KRBErr
+ }
+ return b, nil
+}
+
+// TGStoHashcat converts a TGS to a hashcat format.
+func TGStoHashcat(tgs messages.Ticket, username string) (string, error) {
+ return fmt.Sprintf("$krb5tgs$%d$*%s$%s$%s*$%s$%s",
+ tgs.EncPart.EType,
+ username,
+ tgs.Realm,
+ strings.Join(tgs.SName.NameString[:], "/"),
+ hex.EncodeToString(tgs.EncPart.Cipher[:16]),
+ hex.EncodeToString(tgs.EncPart.Cipher[16:]),
+ ), nil
+}
+
+// ASRepToHashcat converts an AS-REP message to a hashcat format
+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/pkg/js/libs/ldap/adenum.go b/pkg/js/libs/ldap/adenum.go
new file mode 100644
index 0000000000..23d2a4fc11
--- /dev/null
+++ b/pkg/js/libs/ldap/adenum.go
@@ -0,0 +1,269 @@
+package ldap
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/go-ldap/ldap/v3"
+)
+
+// LDAP makes you search using an OID
+// http://oid-info.com/get/1.2.840.113556.1.4.803
+//
+// The one for the userAccountControl in MS Active Directory is
+// 1.2.840.113556.1.4.803 (LDAP_MATCHING_RULE_BIT_AND)
+//
+// We can look at the enabled flags using a query like (!(userAccountControl:1.2.840.113556.1.4.803:=2))
+//
+// https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
+const (
+ FilterIsPerson = "(objectCategory=person)" // The object is a person.
+ FilterIsGroup = "(objectCategory=group)" // The object is a group.
+ FilterIsComputer = "(objectCategory=computer)" // The object is a computer.
+ FilterIsAdmin = "(adminCount=1)" // The object is an admin.
+ FilterHasServicePrincipalName = "(servicePrincipalName=*)" // The object has a service principal name.
+ FilterLogonScript = "(userAccountControl:1.2.840.113556.1.4.803:=1)" // The logon script will be run.
+ FilterAccountDisabled = "(userAccountControl:1.2.840.113556.1.4.803:=2)" // The user account is disabled.
+ FilterAccountEnabled = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))" // The user account is enabled.
+ FilterHomedirRequired = "(userAccountControl:1.2.840.113556.1.4.803:=8)" // The home folder is required.
+ FilterLockout = "(userAccountControl:1.2.840.113556.1.4.803:=16)" // The user is locked out.
+ FilterPasswordNotRequired = "(userAccountControl:1.2.840.113556.1.4.803:=32)" // No password is required.
+ FilterPasswordCantChange = "(userAccountControl:1.2.840.113556.1.4.803:=64)" // The user can't change the password.
+ FilterCanSendEncryptedPassword = "(userAccountControl:1.2.840.113556.1.4.803:=128)" // The user can send an encrypted password.
+ FilterIsDuplicateAccount = "(userAccountControl:1.2.840.113556.1.4.803:=256)" // It's an account for users whose primary account is in another domain.
+ FilterIsNormalAccount = "(userAccountControl:1.2.840.113556.1.4.803:=512)" // It's a default account type that represents a typical user.
+ FilterInterdomainTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=2048)" // It's a permit to trust an account for a system domain that trusts other domains.
+ FilterWorkstationTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=4096)" // It's a computer account for a computer that is running old Windows builds.
+ FilterServerTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=8192)" // It's a computer account for a domain controller that is a member of this domain.
+ FilterDontExpirePassword = "(userAccountControl:1.2.840.113556.1.4.803:=65536)" // Represents the password, which should never expire on the account.
+ FilterMnsLogonAccount = "(userAccountControl:1.2.840.113556.1.4.803:=131072)" // It's an MNS logon account.
+ FilterSmartCardRequired = "(userAccountControl:1.2.840.113556.1.4.803:=262144)" // When this flag is set, it forces the user to log on by using a smart card.
+ FilterTrustedForDelegation = "(userAccountControl:1.2.840.113556.1.4.803:=524288)" // When this flag is set, the service account (the user or computer account) under which a service runs is trusted for Kerberos delegation.
+ FilterNotDelegated = "(userAccountControl:1.2.840.113556.1.4.803:=1048576)" // When this flag is set, the security context of the user isn't delegated to a service even if the service account is set as trusted for Kerberos delegation.
+ FilterUseDesKeyOnly = "(userAccountControl:1.2.840.113556.1.4.803:=2097152)" // Restrict this principal to use only Data Encryption Standard (DES) encryption types for keys.
+ FilterDontRequirePreauth = "(userAccountControl:1.2.840.113556.1.4.803:=4194304)" // This account doesn't require Kerberos pre-authentication for logging on.
+ FilterPasswordExpired = "(userAccountControl:1.2.840.113556.1.4.803:=8388608)" // The user's password has expired.
+ FilterTrustedToAuthForDelegation = "(userAccountControl:1.2.840.113556.1.4.803:=16777216)" // The account is enabled for delegation.
+ FilterPartialSecretsAccount = "(userAccountControl:1.2.840.113556.1.4.803:=67108864)" // The account is a read-only domain controller (RODC).
+
+)
+
+// JoinFilters joins multiple filters into a single filter
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const filter = ldap.JoinFilters(ldap.FilterIsPerson, ldap.FilterAccountEnabled);
+// ```
+func JoinFilters(filters ...string) string {
+ var builder strings.Builder
+ builder.WriteString("(&")
+ for _, s := range filters {
+ builder.WriteString(s)
+ }
+ builder.WriteString(")")
+ return builder.String()
+}
+
+// NegativeFilter returns a negative filter for a given filter
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const filter = ldap.NegativeFilter(ldap.FilterIsPerson);
+// ```
+func NegativeFilter(filter string) string {
+ return fmt.Sprintf("(!%s)", filter)
+}
+
+type (
+ // ADObject represents an Active Directory object
+ // @example
+ // ```javascript
+ // const ldap = require('nuclei/ldap');
+ // const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ // const users = client.GetADUsers();
+ // log(to_json(users));
+ // ```
+ ADObject struct {
+ DistinguishedName string
+ SAMAccountName string
+ PWDLastSet string
+ LastLogon string
+ MemberOf []string
+ ServicePrincipalName []string
+ }
+)
+
+// FindADObjects finds AD objects based on a filter
+// and returns them as a list of ADObject
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const users = client.FindADObjects(ldap.FilterIsPerson);
+// log(to_json(users));
+// ```
+func (c *Client) FindADObjects(filter string) []ADObject {
+ c.nj.Require(c.conn != nil, "no existing connection")
+ sr := ldap.NewSearchRequest(
+ c.BaseDN, ldap.ScopeWholeSubtree,
+ ldap.NeverDerefAliases, 0, 0, false,
+ filter,
+ []string{
+ "distinguishedName",
+ "sAMAccountName",
+ "pwdLastSet",
+ "lastLogon",
+ "memberOf",
+ "servicePrincipalName",
+ },
+ nil,
+ )
+
+ res, err := c.conn.Search(sr)
+ c.nj.HandleError(err, "ldap search request failed")
+
+ var objects []ADObject
+ for _, obj := range res.Entries {
+ objects = append(objects, ADObject{
+ DistinguishedName: obj.GetAttributeValue("distinguishedName"),
+ SAMAccountName: obj.GetAttributeValue("sAMAccountName"),
+ PWDLastSet: DecodeADTimestamp(obj.GetAttributeValue("pwdLastSet")),
+ LastLogon: DecodeADTimestamp(obj.GetAttributeValue("lastLogon")),
+ MemberOf: obj.GetAttributeValues("memberOf"),
+ ServicePrincipalName: obj.GetAttributeValues("servicePrincipalName"),
+ })
+ }
+ return objects
+}
+
+// GetADUsers returns all AD users
+// using FilterIsPerson filter query
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const users = client.GetADUsers();
+// log(to_json(users));
+// ```
+func (c *Client) GetADUsers() []ADObject {
+ return c.FindADObjects(FilterIsPerson)
+}
+
+// GetADActiveUsers returns all AD users
+// using FilterIsPerson and FilterAccountEnabled filter query
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const users = client.GetADActiveUsers();
+// log(to_json(users));
+// ```
+func (c *Client) GetADActiveUsers() []ADObject {
+ return c.FindADObjects(JoinFilters(FilterIsPerson, FilterAccountEnabled))
+}
+
+// GetAdUserWithNeverExpiringPasswords returns all AD users
+// using FilterIsPerson and FilterDontExpirePassword filter query
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const users = client.GetADUserWithNeverExpiringPasswords();
+// log(to_json(users));
+// ```
+func (c *Client) GetADUserWithNeverExpiringPasswords() []ADObject {
+ return c.FindADObjects(JoinFilters(FilterIsPerson, FilterDontExpirePassword))
+}
+
+// GetADUserTrustedForDelegation returns all AD users that are trusted for delegation
+// using FilterIsPerson and FilterTrustedForDelegation filter query
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const users = client.GetADUserTrustedForDelegation();
+// log(to_json(users));
+// ```
+func (c *Client) GetADUserTrustedForDelegation() []ADObject {
+ return c.FindADObjects(JoinFilters(FilterIsPerson, FilterTrustedForDelegation))
+}
+
+// GetADUserWithPasswordNotRequired returns all AD users that do not require a password
+// using FilterIsPerson and FilterPasswordNotRequired filter query
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const users = client.GetADUserWithPasswordNotRequired();
+// log(to_json(users));
+// ```
+func (c *Client) GetADUserWithPasswordNotRequired() []ADObject {
+ return c.FindADObjects(JoinFilters(FilterIsPerson, FilterPasswordNotRequired))
+}
+
+// GetADGroups returns all AD groups
+// using FilterIsGroup filter query
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const groups = client.GetADGroups();
+// log(to_json(groups));
+// ```
+func (c *Client) GetADGroups() []ADObject {
+ return c.FindADObjects(FilterIsGroup)
+}
+
+// GetADDCList returns all AD domain controllers
+// using FilterIsComputer, FilterAccountEnabled and FilterServerTrustAccount filter query
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const dcs = client.GetADDCList();
+// log(to_json(dcs));
+// ```
+func (c *Client) GetADDCList() []ADObject {
+ return c.FindADObjects(JoinFilters(FilterIsComputer, FilterAccountEnabled, FilterServerTrustAccount))
+}
+
+// GetADAdmins returns all AD admins
+// using FilterIsPerson, FilterAccountEnabled and FilterIsAdmin filter query
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const admins = client.GetADAdmins();
+// log(to_json(admins));
+// ```
+func (c *Client) GetADAdmins() []ADObject {
+ return c.FindADObjects(JoinFilters(FilterIsPerson, FilterAccountEnabled, FilterIsAdmin))
+}
+
+// GetADUserKerberoastable returns all AD users that are kerberoastable
+// using FilterIsPerson, FilterAccountEnabled and FilterHasServicePrincipalName filter query
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const kerberoastable = client.GetADUserKerberoastable();
+// log(to_json(kerberoastable));
+// ```
+func (c *Client) GetADUserKerberoastable() []ADObject {
+ return c.FindADObjects(JoinFilters(FilterIsPerson, FilterAccountEnabled, FilterHasServicePrincipalName))
+}
+
+// GetADDomainSID returns the SID of the AD domain
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const domainSID = client.GetADDomainSID();
+// log(domainSID);
+// ```
+func (c *Client) GetADDomainSID() string {
+ r := c.Search(FilterServerTrustAccount, "objectSid")
+ c.nj.Require(len(r) > 0, "no result from GetADDomainSID query")
+ c.nj.Require(len(r[0]["objectSid"]) > 0, "could not grab DomainSID")
+ return DecodeSID(r[0]["objectSid"][0])
+}
diff --git a/pkg/js/libs/ldap/ldap.go b/pkg/js/libs/ldap/ldap.go
index 1060df0e27..5f03611cb2 100644
--- a/pkg/js/libs/ldap/ldap.go
+++ b/pkg/js/libs/ldap/ldap.go
@@ -2,132 +2,296 @@ package ldap
import (
"context"
+ "crypto/tls"
"fmt"
+ "net"
+ "net/url"
"strings"
- "time"
+ "github.com/dop251/goja"
"github.com/go-ldap/ldap/v3"
- "github.com/praetorian-inc/fingerprintx/pkg/plugins"
+ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
"github.com/projectdiscovery/nuclei/v3/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)
+type (
+ // Client is a client for ldap protocol in nuclei
+ // @example
+ // ```javascript
+ // const ldap = require('nuclei/ldap');
+ // // here ldap.example.com is the ldap server and acme.com is the realm
+ // const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+ // ```
+ // @example
+ // ```javascript
+ // const ldap = require('nuclei/ldap');
+ // const cfg = new ldap.Config();
+ // cfg.Timeout = 10;
+ // cfg.ServerName = 'ldap.internal.acme.com';
+ // // optional config can be passed as third argument
+ // const client = new ldap.Client('ldap://ldap.example.com', 'acme.com', cfg);
+ // ```
+ Client struct {
+ Host string // Hostname
+ Port int // Port
+ Realm string // Realm
+ BaseDN string // BaseDN (generated from Realm)
+
+ // unexported
+ nj *utils.NucleiJS // nuclei js utils
+ conn *ldap.Conn
+ cfg Config
}
+)
- timeout := 10 * time.Second
-
- conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))
-
- if err != nil {
- return false, err
+type (
+ // Config is extra configuration for the ldap client
+ // @example
+ // ```javascript
+ // const ldap = require('nuclei/ldap');
+ // const cfg = new ldap.Config();
+ // cfg.Timeout = 10;
+ // cfg.ServerName = 'ldap.internal.acme.com';
+ // cfg.Upgrade = true; // upgrade to tls
+ // ```
+ Config struct {
+ // Timeout is the timeout for the ldap client in seconds
+ Timeout int
+ ServerName string // default to host (when using tls)
+ Upgrade bool // when true first connects to non-tls and then upgrades to tls
}
- defer conn.Close()
+)
- _ = conn.SetDeadline(time.Now().Add(timeout))
+// Constructor for creating a new ldap client
+// The following schemas are supported for url: ldap://, ldaps://, ldapi://,
+// and cldap:// (RFC1798, deprecated but used by Active Directory).
+// ldaps uses TLS/SSL, ldapi uses a Unix domain socket, and cldap uses connectionless LDAP.
+// Constructor: constructor(public ldapUrl: string, public realm: string, public config?: Config)
+func NewClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
+ // setup nucleijs utils
+ c := &Client{nj: utils.NewNucleiJS(runtime)}
+ c.nj.ObjectSig = "Client(ldapUrl,Realm,{Config})" // will be included in error messages
+
+ // get arguments (type assertion is efficient than reflection)
+ ldapUrl, _ := c.nj.GetArg(call.Arguments, 0).(string)
+ realm, _ := c.nj.GetArg(call.Arguments, 1).(string)
+ c.cfg = utils.GetStructTypeSafe[Config](c.nj, call.Arguments, 2, Config{})
+ c.Realm = realm
+ c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(realm, "."), ",dc="))
+
+ // validate arguments
+ c.nj.Require(ldapUrl != "", "ldap url cannot be empty")
+ c.nj.Require(realm != "", "realm cannot be empty")
+
+ u, err := url.Parse(ldapUrl)
+ c.nj.HandleError(err, "invalid ldap url supported schemas are ldap://, ldaps://, ldapi://, and cldap://")
+
+ var conn net.Conn
+ if u.Scheme == "ldapi" {
+ if u.Path == "" || u.Path == "/" {
+ u.Path = "/var/run/slapd/ldapi"
+ }
+ conn, err = protocolstate.Dialer.Dial(context.TODO(), "unix", u.Path)
+ c.nj.HandleError(err, "failed to connect to ldap server")
+ } else {
+ host, port, err := net.SplitHostPort(u.Host)
+ if err != nil {
+ // we assume that error is due to missing port
+ host = u.Host
+ port = ""
+ }
+ if u.Scheme == "" {
+ // default to ldap
+ u.Scheme = "ldap"
+ }
- plugin := &pluginldap.LDAPPlugin{}
- service, err := plugin.Run(conn, timeout, plugins.Target{Host: host})
- if err != nil {
- return false, err
+ switch u.Scheme {
+ case "cldap":
+ if port == "" {
+ port = ldap.DefaultLdapPort
+ }
+ conn, err = protocolstate.Dialer.Dial(context.TODO(), "udp", net.JoinHostPort(host, port))
+ case "ldap":
+ if port == "" {
+ port = ldap.DefaultLdapPort
+ }
+ conn, err = protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, port))
+ case "ldaps":
+ if port == "" {
+ port = ldap.DefaultLdapsPort
+ }
+ serverName := host
+ if c.cfg.ServerName != "" {
+ serverName = c.cfg.ServerName
+ }
+ conn, err = protocolstate.Dialer.DialTLSWithConfig(context.TODO(), "tcp", net.JoinHostPort(host, port),
+ &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10, ServerName: serverName})
+ default:
+ err = fmt.Errorf("unsupported ldap url schema %v", u.Scheme)
+ }
+ c.nj.HandleError(err, "failed to connect to ldap server")
}
- if service == nil {
- return false, nil
+ c.conn = ldap.NewConn(conn, u.Scheme == "ldaps")
+ if u.Scheme != "ldaps" && c.cfg.Upgrade {
+ serverName := u.Hostname()
+ if c.cfg.ServerName != "" {
+ serverName = c.cfg.ServerName
+ }
+ if err := c.conn.StartTLS(&tls.Config{InsecureSkipVerify: true, ServerName: serverName}); err != nil {
+ c.nj.HandleError(err, "failed to upgrade to tls")
+ }
+ } else {
+ c.conn.Start()
}
- return true, nil
+
+ return utils.LinkConstructor(call, runtime, c)
}
-// CollectLdapMetadata collects metadata from ldap server.
-func (c *LdapClient) CollectLdapMetadata(domain string, controller string) (LDAPMetadata, error) {
- opts := &ldapSessionOptions{
- domain: domain,
- domainController: controller,
+// Authenticate authenticates with the ldap server using the given username and password
+// performs NTLMBind first and then Bind/UnauthenticatedBind if NTLMBind fails
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// client.Authenticate('user', 'password');
+// ```
+func (c *Client) Authenticate(username, password string) {
+ c.nj.Require(c.conn != nil, "no existing connection")
+ if c.BaseDN == "" {
+ c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
}
-
- if !protocolstate.IsHostAllowed(domain) {
- // host is not valid according to network policy
- return LDAPMetadata{}, protocolstate.ErrHostDenied.Msgf(domain)
+ if err := c.conn.NTLMBind(c.Realm, username, password); err == nil {
+ // if bind with NTLMBind(), there is nothing
+ // else to do, you are authenticated
+ return
}
- conn, err := c.newLdapSession(opts)
- if err != nil {
- return LDAPMetadata{}, err
+ switch password {
+ case "":
+ if err := c.conn.UnauthenticatedBind(username); err != nil {
+ c.nj.ThrowError(err)
+ }
+ default:
+ if err := c.conn.Bind(username, password); err != nil {
+ c.nj.ThrowError(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
+// AuthenticateWithNTLMHash authenticates with the ldap server using the given username and NTLM hash
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// client.AuthenticateWithNTLMHash('pdtm', 'hash');
+// ```
+func (c *Client) AuthenticateWithNTLMHash(username, hash string) {
+ c.nj.Require(c.conn != nil, "no existing connection")
+ if c.BaseDN == "" {
+ c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
+ }
+ if err := c.conn.NTLMBindWithHash(c.Realm, username, hash); err != nil {
+ c.nj.ThrowError(err)
+ }
}
-func (c *LdapClient) newLdapSession(opts *ldapSessionOptions) (*ldap.Conn, error) {
- port := opts.port
- dc := opts.domainController
- if port == 0 {
- port = 389
+// Search accepts whatever filter and returns a list of maps having provided attributes
+// as keys and associated values mirroring the ones returned by ldap
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const results = client.Search('(objectClass=*)', 'cn', 'mail');
+// ```
+func (c *Client) Search(filter string, attributes ...string) []map[string][]string {
+ c.nj.Require(c.conn != nil, "no existing connection")
+
+ res, err := c.conn.Search(
+ ldap.NewSearchRequest(
+ c.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
+ 0, 0, false, filter, attributes, nil,
+ ),
+ )
+ c.nj.HandleError(err, "ldap search request failed")
+ if len(res.Entries) == 0 {
+ // return empty list
+ return nil
}
- conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", dc, port))
- if err != nil {
- return nil, err
+ // convert ldap.Entry to []map[string][]string
+ var out []map[string][]string
+ for _, r := range res.Entries {
+ app := make(map[string][]string)
+ empty := true
+ for _, a := range attributes {
+ v := r.GetAttributeValues(a)
+ if len(v) > 0 {
+ app[a] = v
+ empty = false
+ }
+ }
+ if !empty {
+ out = append(out, app)
+ }
}
-
- lConn := ldap.NewConn(conn, false)
- lConn.Start()
-
- return lConn, nil
+ return out
}
-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
+// AdvancedSearch accepts all values of search request type and return Ldap Entry
+// its up to user to handle the response
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const results = client.AdvancedSearch(ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, '(objectClass=*)', ['cn', 'mail'], []);
+// ```
+func (c *Client) AdvancedSearch(
+ Scope, DerefAliases, SizeLimit, TimeLimit int,
+ TypesOnly bool,
+ Filter string,
+ Attributes []string,
+ Controls []ldap.Control) ldap.SearchResult {
+ c.nj.Require(c.conn != nil, "no existing connection")
+ if c.BaseDN == "" {
+ c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
+ }
+ req := ldap.NewSearchRequest(c.BaseDN, Scope, DerefAliases, SizeLimit, TimeLimit, TypesOnly, Filter, Attributes, Controls)
+ res, err := c.conn.Search(req)
+ c.nj.HandleError(err, "ldap search request failed")
+ c.nj.Require(res != nil, "ldap search request failed got nil response")
+ return *res
}
-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
+type (
+ // Metadata is the metadata for ldap server.
+ // this is returned by CollectMetadata method
+ Metadata struct {
+ BaseDN string
+ Domain string
+ DefaultNamingContext string
+ DomainFunctionality string
+ ForestFunctionality string
+ DomainControllerFunctionality string
+ DnsHostName string
}
+)
- baseDN, _ := getBaseNamingContext(opts, lConn)
-
- metadata.BaseDN = baseDN
- metadata.Domain = parseDC(baseDN)
+// CollectLdapMetadata collects metadata from ldap server.
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// const metadata = client.CollectMetadata();
+// log(to_json(metadata));
+// ```
+func (c *Client) CollectMetadata() Metadata {
+ c.nj.Require(c.conn != nil, "no existing connection")
+ var metadata Metadata
+ metadata.Domain = c.Realm
+ if c.BaseDN == "" {
+ c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
+ }
+ metadata.BaseDN = c.BaseDN
srMetadata := ldap.NewSearchRequest(
"",
@@ -143,10 +307,9 @@ func (c *LdapClient) collectLdapMetadata(lConn *ldap.Conn, opts *ldapSessionOpti
"dnsHostName",
},
nil)
- resMetadata, err := lConn.Search(srMetadata)
- if err != nil {
- return metadata, err
- }
+ resMetadata, err := c.conn.Search(srMetadata)
+ c.nj.HandleError(err, "ldap search request failed")
+
for _, entry := range resMetadata.Entries {
for _, attr := range entry.Attributes {
value := entry.GetAttributeValue(attr.Name)
@@ -164,142 +327,16 @@ func (c *LdapClient) collectLdapMetadata(lConn *ldap.Conn, opts *ldapSessionOpti
}
}
}
- return metadata, nil
+ return metadata
}
-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
-}
-
-// KerberoastableUser contains the important fields of the Active Directory
-// kerberoastable user
-type KerberoastableUser struct {
- SAMAccountName string
- ServicePrincipalName string
- PWDLastSet string
- MemberOf string
- UserAccountControl string
- LastLogon string
-}
-
-// GetKerberoastableUsers collects all "person" users that have an SPN
-// associated with them. The LDAP filter is built with the same logic as
-// "GetUserSPNs.py", the well-known impacket example by Forta.
-// https://github.com/fortra/impacket/blob/master/examples/GetUserSPNs.py#L297
-//
-// Returns a list of KerberoastableUser, if an error occurs, returns an empty
-// slice and the raised error
-func (c *LdapClient) GetKerberoastableUsers(domain, controller string, username, password string) ([]KerberoastableUser, error) {
- opts := &ldapSessionOptions{
- domain: domain,
- domainController: controller,
- username: username,
- password: password,
- }
-
- if !protocolstate.IsHostAllowed(domain) {
- // host is not valid according to network policy
- return nil, protocolstate.ErrHostDenied.Msgf(domain)
- }
-
- conn, err := c.newLdapSession(opts)
- if err != nil {
- return nil, err
- }
- defer c.close(conn)
-
- domainParts := strings.Split(domain, ".")
- if username == "" {
- err = conn.UnauthenticatedBind("")
- } else {
- err = conn.Bind(
- fmt.Sprintf("%v\\%v", domainParts[0], username),
- password,
- )
- }
- if err != nil {
- return nil, err
- }
-
- var baseDN strings.Builder
- for i, part := range domainParts {
- baseDN.WriteString("DC=")
- baseDN.WriteString(part)
- if i != len(domainParts)-1 {
- baseDN.WriteString(",")
- }
- }
-
- sr := ldap.NewSearchRequest(
- baseDN.String(),
- ldap.ScopeWholeSubtree,
- ldap.NeverDerefAliases,
- 0, 0, false,
- // (&(is_user) (!(account_is_disabled)) (has_SPN))
- "(&(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(servicePrincipalName=*))",
- []string{
- "SAMAccountName",
- "ServicePrincipalName",
- "pwdLastSet",
- "MemberOf",
- "userAccountControl",
- "lastLogon",
- },
- nil,
- )
-
- res, err := conn.Search(sr)
- if err != nil {
- return nil, err
- }
-
- if len(res.Entries) == 0 {
- return nil, fmt.Errorf("no kerberoastable user found")
- }
-
- var ku []KerberoastableUser
- for _, usr := range res.Entries {
- ku = append(ku, KerberoastableUser{
- SAMAccountName: usr.GetAttributeValue("sAMAccountName"),
- ServicePrincipalName: usr.GetAttributeValue("servicePrincipalName"),
- PWDLastSet: usr.GetAttributeValue("pwdLastSet"),
- MemberOf: usr.GetAttributeValue("MemberOf"),
- UserAccountControl: usr.GetAttributeValue("userAccountControl"),
- LastLogon: usr.GetAttributeValue("lastLogon"),
- })
- }
- return ku, nil
+// close the ldap connection
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
+// client.Close();
+// ```
+func (c *Client) Close() {
+ c.conn.Close()
}
diff --git a/pkg/js/libs/ldap/utils.go b/pkg/js/libs/ldap/utils.go
new file mode 100644
index 0000000000..96a243bbc7
--- /dev/null
+++ b/pkg/js/libs/ldap/utils.go
@@ -0,0 +1,79 @@
+package ldap
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// DecodeSID decodes a SID string
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const sid = ldap.DecodeSID('S-1-5-21-3623811015-3361044348-30300820-1013');
+// log(sid);
+// ```
+func DecodeSID(s string) string {
+ b := []byte(s)
+ revisionLvl := int(b[0])
+ subAuthorityCount := int(b[1]) & 0xFF
+
+ var authority int
+ for i := 2; i <= 7; i++ {
+ authority = authority | int(b[i])<<(8*(5-(i-2)))
+ }
+
+ var size = 4
+ var offset = 8
+ var subAuthorities []int
+ for i := 0; i < subAuthorityCount; i++ {
+ var subAuthority int
+ for k := 0; k < size; k++ {
+ subAuthority = subAuthority | (int(b[offset+k])&0xFF)<<(8*k)
+ }
+ subAuthorities = append(subAuthorities, subAuthority)
+ offset += size
+ }
+
+ var builder strings.Builder
+ builder.WriteString("S-")
+ builder.WriteString(fmt.Sprintf("%d-", revisionLvl))
+ builder.WriteString(fmt.Sprintf("%d", authority))
+ for _, v := range subAuthorities {
+ builder.WriteString(fmt.Sprintf("-%d", v))
+ }
+ return builder.String()
+}
+
+// DecodeADTimestamp decodes an Active Directory timestamp
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const timestamp = ldap.DecodeADTimestamp('132036744000000000');
+// log(timestamp);
+// ```
+func DecodeADTimestamp(timestamp string) string {
+ adtime, _ := strconv.ParseInt(timestamp, 10, 64)
+ if (adtime == 9223372036854775807) || (adtime == 0) {
+ return "Not Set"
+ }
+ unixtime_int64 := adtime/(10*1000*1000) - 11644473600
+ unixtime := time.Unix(unixtime_int64, 0)
+ return unixtime.Format("2006-01-02 3:4:5 pm")
+}
+
+// DecodeZuluTimestamp decodes a Zulu timestamp
+// @example
+// ```javascript
+// const ldap = require('nuclei/ldap');
+// const timestamp = ldap.DecodeZuluTimestamp('2021-08-25T10:00:00Z');
+// log(timestamp);
+// ```
+func DecodeZuluTimestamp(timestamp string) string {
+ zulu, err := time.Parse(time.RFC3339, timestamp)
+ if err != nil {
+ return ""
+ }
+ return zulu.Format("2006-01-02 3:4:5 pm")
+}
diff --git a/pkg/js/libs/mssql/memo.mssql.go b/pkg/js/libs/mssql/memo.mssql.go
new file mode 100755
index 0000000000..eb169761a0
--- /dev/null
+++ b/pkg/js/libs/mssql/memo.mssql.go
@@ -0,0 +1,43 @@
+// Warning - This is generated code
+package mssql
+
+import (
+ "errors"
+ "fmt"
+
+ _ "github.com/denisenkom/go-mssqldb"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedconnect(host string, port int, username string, password string, dbName string) (bool, error) {
+ hash := "connect" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return connect(host, port, username, password, dbName)
+ })
+ if err != nil {
+ return false, err
+ }
+ if value, ok := v.(bool); ok {
+ return value, nil
+ }
+
+ return false, errors.New("could not convert cached result")
+}
+
+func memoizedisMssql(host string, port int) (bool, error) {
+ hash := "isMssql" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return isMssql(host, port)
+ })
+ if err != nil {
+ return false, err
+ }
+ if value, ok := v.(bool); ok {
+ return value, nil
+ }
+
+ return false, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/mssql/mssql.go b/pkg/js/libs/mssql/mssql.go
index 79fe58157b..1b0efa4284 100644
--- a/pkg/js/libs/mssql/mssql.go
+++ b/pkg/js/libs/mssql/mssql.go
@@ -14,32 +14,47 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
-// Client is a client for MS SQL database.
-//
-// Internally client uses denisenkom/go-mssqldb driver.
-type MSSQLClient struct{}
+type (
+ // Client is a client for MS SQL database.
+ // Internally client uses denisenkom/go-mssqldb driver.
+ // @example
+ // ```javascript
+ // const mssql = require('nuclei/mssql');
+ // const client = new mssql.MSSQLClient;
+ // ```
+ 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.
+// @example
+// ```javascript
+// const mssql = require('nuclei/mssql');
+// const client = new mssql.MSSQLClient;
+// const connected = client.Connect('acme.com', 1433, 'username', 'password');
+// ```
func (c *MSSQLClient) Connect(host string, port int, username, password string) (bool, error) {
- return connect(host, port, username, password, "master")
+ return memoizedconnect(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.
+// @example
+// ```javascript
+// const mssql = require('nuclei/mssql');
+// const client = new mssql.MSSQLClient;
+// const connected = client.ConnectWithDB('acme.com', 1433, 'username', 'password', 'master');
+// ```
func (c *MSSQLClient) ConnectWithDB(host string, port int, username, password, dbName string) (bool, error) {
- return connect(host, port, username, password, dbName)
+ return memoizedconnect(host, port, username, password, dbName)
}
-func connect(host string, port int, username, password, dbName string) (bool, error) {
+// @memo
+func connect(host string, port int, username string, password string, dbName string) (bool, error) {
if host == "" || port <= 0 {
return false, fmt.Errorf("invalid host or port")
}
@@ -82,10 +97,19 @@ func connect(host string, port int, username, password, dbName string) (bool, er
}
// 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.
+// @example
+// ```javascript
+// const mssql = require('nuclei/mssql');
+// const isMssql = mssql.IsMssql('acme.com', 1433);
+// ```
func (c *MSSQLClient) IsMssql(host string, port int) (bool, error) {
+ return memoizedisMssql(host, port)
+}
+
+// @memo
+func 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)
diff --git a/pkg/js/libs/mysql/memo.mysql.go b/pkg/js/libs/mysql/memo.mysql.go
new file mode 100755
index 0000000000..60fda434c5
--- /dev/null
+++ b/pkg/js/libs/mysql/memo.mysql.go
@@ -0,0 +1,41 @@
+// Warning - This is generated code
+package mysql
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedisMySQL(host string, port int) (bool, error) {
+ hash := "isMySQL" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return isMySQL(host, port)
+ })
+ if err != nil {
+ return false, err
+ }
+ if value, ok := v.(bool); ok {
+ return value, nil
+ }
+
+ return false, errors.New("could not convert cached result")
+}
+
+func memoizedfingerprintMySQL(host string, port int) (MySQLInfo, error) {
+ hash := "fingerprintMySQL" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return fingerprintMySQL(host, port)
+ })
+ if err != nil {
+ return MySQLInfo{}, err
+ }
+ if value, ok := v.(MySQLInfo); ok {
+ return value, nil
+ }
+
+ return MySQLInfo{}, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/mysql/memo.mysql_private.go b/pkg/js/libs/mysql/memo.mysql_private.go
new file mode 100755
index 0000000000..19d7e81b0a
--- /dev/null
+++ b/pkg/js/libs/mysql/memo.mysql_private.go
@@ -0,0 +1,25 @@
+// Warning - This is generated code
+package mysql
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedconnectWithDSN(dsn string) (bool, error) {
+ hash := "connectWithDSN" + ":" + fmt.Sprint(dsn)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return connectWithDSN(dsn)
+ })
+ if err != nil {
+ return false, err
+ }
+ if value, ok := v.(bool); ok {
+ return value, nil
+ }
+
+ return false, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/mysql/mysql.go b/pkg/js/libs/mysql/mysql.go
index b5911ae701..0fd2f24200 100644
--- a/pkg/js/libs/mysql/mysql.go
+++ b/pkg/js/libs/mysql/mysql.go
@@ -16,16 +16,31 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
-// MySQLClient is a client for MySQL database.
-//
-// Internally client uses go-sql-driver/mysql driver.
-type MySQLClient struct{}
+type (
+ // MySQLClient is a client for MySQL database.
+ // Internally client uses go-sql-driver/mysql driver.
+ // @example
+ // ```javascript
+ // const mysql = require('nuclei/mysql');
+ // const client = new mysql.MySQLClient;
+ // ```
+ MySQLClient struct{}
+)
// 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.
+// @example
+// ```javascript
+// const mysql = require('nuclei/mysql');
+// const isMySQL = mysql.IsMySQL('acme.com', 3306);
+// ```
func (c *MySQLClient) IsMySQL(host string, port int) (bool, error) {
+ return memoizedisMySQL(host, port)
+}
+
+// @memo
+func 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)
@@ -48,10 +63,15 @@ func (c *MySQLClient) IsMySQL(host string, port int) (bool, error) {
}
// 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.
+// @example
+// ```javascript
+// const mysql = require('nuclei/mysql');
+// const client = new mysql.MySQLClient;
+// const connected = client.Connect('acme.com', 3306, 'username', 'password');
+// ```
func (c *MySQLClient) Connect(host string, port int, username, password string) (bool, error) {
if !protocolstate.IsHostAllowed(host) {
// host is not valid according to network policy
@@ -71,20 +91,35 @@ func (c *MySQLClient) Connect(host string, port int, username, password string)
return connectWithDSN(dsn)
}
-type MySQLInfo struct {
- Host string `json:"host,omitempty"`
- IP string `json:"ip"`
- Port int `json:"port"`
- Protocol string `json:"protocol"`
- TLS bool `json:"tls"`
- Transport string `json:"transport"`
- Version string `json:"version,omitempty"`
- Debug plugins.ServiceMySQL `json:"debug,omitempty"`
- Raw string `json:"metadata"`
-}
+type (
+ // MySQLInfo contains information about MySQL server.
+ // this is returned when fingerprint is successful
+ MySQLInfo struct {
+ Host string `json:"host,omitempty"`
+ IP string `json:"ip"`
+ Port int `json:"port"`
+ Protocol string `json:"protocol"`
+ TLS bool `json:"tls"`
+ Transport string `json:"transport"`
+ Version string `json:"version,omitempty"`
+ Debug plugins.ServiceMySQL `json:"debug,omitempty"`
+ Raw string `json:"metadata"`
+ }
+)
// returns MySQLInfo when fingerpint is successful
+// @example
+// ```javascript
+// const mysql = require('nuclei/mysql');
+// const info = mysql.FingerprintMySQL('acme.com', 3306);
+// log(to_json(info));
+// ```
func (c *MySQLClient) FingerprintMySQL(host string, port int) (MySQLInfo, error) {
+ return memoizedfingerprintMySQL(host, port)
+}
+
+// @memo
+func fingerprintMySQL(host string, port int) (MySQLInfo, error) {
info := MySQLInfo{}
if !protocolstate.IsHostAllowed(host) {
// host is not valid according to network policy
@@ -120,10 +155,28 @@ func (c *MySQLClient) FingerprintMySQL(host string, port int) (MySQLInfo, error)
// ConnectWithDSN connects to MySQL database using given DSN.
// we override mysql dialer with fastdialer so it respects network policy
+// If connection is successful, it returns true.
+// @example
+// ```javascript
+// const mysql = require('nuclei/mysql');
+// const client = new mysql.MySQLClient;
+// const connected = client.ConnectWithDSN('username:password@tcp(acme.com:3306)/');
+// ```
func (c *MySQLClient) ConnectWithDSN(dsn string) (bool, error) {
- return connectWithDSN(dsn)
+ return memoizedconnectWithDSN(dsn)
}
+// ExecuteQueryWithOpts connects to Mysql database using given credentials
+// and executes a query on the db.
+// @example
+// ```javascript
+// const mysql = require('nuclei/mysql');
+// const options = new mysql.MySQLOptions();
+// options.Host = 'acme.com';
+// options.Port = 3306;
+// const result = mysql.ExecuteQueryWithOpts(options, 'SELECT * FROM users');
+// log(to_json(result));
+// ```
func (c *MySQLClient) ExecuteQueryWithOpts(opts MySQLOptions, query string) (*utils.SQLResult, error) {
if !protocolstate.IsHostAllowed(opts.Host) {
// host is not valid according to network policy
@@ -160,6 +213,12 @@ func (c *MySQLClient) ExecuteQueryWithOpts(opts MySQLOptions, query string) (*ut
// ExecuteQuery connects to Mysql database using given credentials
// and executes a query on the db.
+// @example
+// ```javascript
+// const mysql = require('nuclei/mysql');
+// const result = mysql.ExecuteQuery('acme.com', 3306, 'username', 'password', 'SELECT * FROM users');
+// log(to_json(result));
+// ```
func (c *MySQLClient) ExecuteQuery(host string, port int, username, password, query string) (*utils.SQLResult, error) {
return c.ExecuteQueryWithOpts(MySQLOptions{
Host: host,
@@ -172,6 +231,12 @@ func (c *MySQLClient) ExecuteQuery(host string, port int, username, password, qu
// ExecuteQuery connects to Mysql database using given credentials
// and executes a query on the db.
+// @example
+// ```javascript
+// const mysql = require('nuclei/mysql');
+// const result = mysql.ExecuteQueryOnDB('acme.com', 3306, 'username', 'password', 'dbname', 'SELECT * FROM users');
+// log(to_json(result));
+// ```
func (c *MySQLClient) ExecuteQueryOnDB(host string, port int, username, password, dbname, query string) (*utils.SQLResult, error) {
return c.ExecuteQueryWithOpts(MySQLOptions{
Host: host,
diff --git a/pkg/js/libs/mysql/mysql_private.go b/pkg/js/libs/mysql/mysql_private.go
index c0e47c04c4..aa89df2600 100644
--- a/pkg/js/libs/mysql/mysql_private.go
+++ b/pkg/js/libs/mysql/mysql_private.go
@@ -8,20 +8,37 @@ import (
"strings"
)
-// MySQLOptions defines the data source name (DSN) options required to connect to a MySQL database.
-// along with other options like Timeout etc
-type MySQLOptions struct {
- Host string // Host is the host name or IP address of the MySQL server.
- Port int // Port is the port number on which the MySQL server is listening.
- Protocol string // Protocol is the protocol used to connect to the MySQL server (ex: "tcp").
- Username string // Username is the user name used to authenticate with the MySQL server.
- Password string // Password is the password used to authenticate with the MySQL server.
- DbName string // DbName is the name of the database to connect to on the MySQL server.
- RawQuery string // QueryStr is the query string to append to the DSN (ex: "?tls=skip-verify").
- Timeout int // Timeout is the timeout in seconds for the connection to the MySQL server.
-}
+type (
+ // MySQLOptions defines the data source name (DSN) options required to connect to a MySQL database.
+ // along with other options like Timeout etc
+ // @example
+ // ```javascript
+ // const mysql = require('nuclei/mysql');
+ // const options = new mysql.MySQLOptions();
+ // options.Host = 'acme.com';
+ // options.Port = 3306;
+ // ```
+ MySQLOptions struct {
+ Host string // Host is the host name or IP address of the MySQL server.
+ Port int // Port is the port number on which the MySQL server is listening.
+ Protocol string // Protocol is the protocol used to connect to the MySQL server (ex: "tcp").
+ Username string // Username is the user name used to authenticate with the MySQL server.
+ Password string // Password is the password used to authenticate with the MySQL server.
+ DbName string // DbName is the name of the database to connect to on the MySQL server.
+ RawQuery string // QueryStr is the query string to append to the DSN (ex: "?tls=skip-verify").
+ Timeout int // Timeout is the timeout in seconds for the connection to the MySQL server.
+ }
+)
// BuildDSN builds a MySQL data source name (DSN) from the given options.
+// @example
+// ```javascript
+// const mysql = require('nuclei/mysql');
+// const options = new mysql.MySQLOptions();
+// options.Host = 'acme.com';
+// options.Port = 3306;
+// const dsn = mysql.BuildDSN(options);
+// ```
func BuildDSN(opts MySQLOptions) (string, error) {
if opts.Host == "" || opts.Port <= 0 {
return "", fmt.Errorf("invalid host or port")
@@ -48,6 +65,7 @@ func BuildDSN(opts MySQLOptions) (string, error) {
return dsn.String(), nil
}
+// @memo
func connectWithDSN(dsn string) (bool, error) {
db, err := sql.Open("mysql", dsn)
if err != nil {
diff --git a/pkg/js/libs/net/net.go b/pkg/js/libs/net/net.go
index 7d5b721719..f1237f0eb1 100644
--- a/pkg/js/libs/net/net.go
+++ b/pkg/js/libs/net/net.go
@@ -20,6 +20,11 @@ var (
// Open opens a new connection to the address with a timeout.
// supported protocols: tcp, udp
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// ```
func Open(protocol, address string) (*NetConn, error) {
conn, err := protocolstate.Dialer.Dial(context.TODO(), protocol, address)
if err != nil {
@@ -30,6 +35,11 @@ func Open(protocol, address string) (*NetConn, error) {
// Open opens a new connection to the address with a timeout.
// supported protocols: tcp, udp
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.OpenTLS('tcp', 'acme.com:443');
+// ```
func OpenTLS(protocol, address string) (*NetConn, error) {
config := &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10}
host, _, _ := net.SplitHostPort(address)
@@ -45,19 +55,39 @@ func OpenTLS(protocol, address string) (*NetConn, error) {
return &NetConn{conn: conn, timeout: defaultTimeout}, nil
}
-// NetConn is a connection to a remote host.
-type NetConn struct {
- conn net.Conn
- timeout time.Duration
-}
+type (
+ // NetConn is a connection to a remote host.
+ // this is returned/create by Open and OpenTLS functions.
+ // @example
+ // ```javascript
+ // const net = require('nuclei/net');
+ // const conn = net.Open('tcp', 'acme.com:80');
+ // ```
+ NetConn struct {
+ conn net.Conn
+ timeout time.Duration
+ }
+)
// Close closes the connection.
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// conn.Close();
+// ```
func (c *NetConn) Close() error {
err := c.conn.Close()
return err
}
// SetTimeout sets read/write timeout for the connection (in seconds).
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// conn.SetTimeout(10);
+// ```
func (c *NetConn) SetTimeout(value int) {
c.timeout = time.Duration(value) * time.Second
}
@@ -77,6 +107,12 @@ func (c *NetConn) unsetDeadLine() {
}
// SendArray sends array data to connection
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// conn.SendArray(['hello', 'world']);
+// ```
func (c *NetConn) SendArray(data []interface{}) error {
c.setDeadLine()
defer c.unsetDeadLine()
@@ -92,6 +128,12 @@ func (c *NetConn) SendArray(data []interface{}) error {
}
// SendHex sends hex data to connection
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// conn.SendHex('68656c6c6f');
+// ```
func (c *NetConn) SendHex(data string) error {
c.setDeadLine()
defer c.unsetDeadLine()
@@ -110,6 +152,12 @@ func (c *NetConn) SendHex(data string) error {
}
// Send sends data to the connection with a timeout.
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// conn.Send('hello');
+// ```
func (c *NetConn) Send(data string) error {
c.setDeadLine()
defer c.unsetDeadLine()
@@ -124,9 +172,16 @@ func (c *NetConn) Send(data string) error {
return nil
}
-// Recv receives data from the connection with a timeout.
+// RecvFull receives data from the connection with a timeout.
// If N is 0, it will read all data sent by the server with 8MB limit.
-func (c *NetConn) Recv(N int) ([]byte, error) {
+// it tries to read until N bytes or timeout is reached.
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// const data = conn.RecvFull(1024);
+// ```
+func (c *NetConn) RecvFull(N int) ([]byte, error) {
c.setDeadLine()
defer c.unsetDeadLine()
if N == 0 {
@@ -140,9 +195,58 @@ func (c *NetConn) Recv(N int) ([]byte, error) {
return bin, nil
}
-// RecvString receives data from the connection with a timeout
+// Recv is similar to RecvFull but does not guarantee full read instead
+// it creates a buffer of N bytes and returns whatever is returned by the connection
+// for reading headers or initial bytes from the server this is usually used.
+// for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// const data = conn.Recv(1024);
+// log(`Received ${data.length} bytes from the server`)
+// ```
+func (c *NetConn) Recv(N int) ([]byte, error) {
+ c.setDeadLine()
+ defer c.unsetDeadLine()
+ if N == 0 {
+ N = 4096
+ }
+ b := make([]byte, N)
+ n, err := c.conn.Read(b)
+ if err != nil {
+ return []byte{}, errorutil.NewWithErr(err).Msgf("failed to read %d bytes", N)
+ }
+ return b[:n], nil
+}
+
+// RecvFullString receives data from the connection with a timeout
// output is returned as a string.
// If N is 0, it will read all data sent by the server with 8MB limit.
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// const data = conn.RecvFullString(1024);
+// ```
+func (c *NetConn) RecvFullString(N int) (string, error) {
+ bin, err := c.RecvFull(N)
+ if err != nil {
+ return "", err
+ }
+ return string(bin), nil
+}
+
+// RecvString is similar to RecvFullString but does not guarantee full read, instead
+// it creates a buffer of N bytes and returns whatever is returned by the connection
+// for reading headers or initial bytes from the server this is usually used.
+// for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFullString.
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// const data = conn.RecvString(1024);
+// ```
func (c *NetConn) RecvString(N int) (string, error) {
bin, err := c.Recv(N)
if err != nil {
@@ -151,9 +255,34 @@ func (c *NetConn) RecvString(N int) (string, error) {
return string(bin), nil
}
-// RecvHex receives data from the connection with a timeout
+// RecvFullHex receives data from the connection with a timeout
// in hex format.
// If N is 0,it will read all data sent by the server with 8MB limit.
+// until N bytes or timeout is reached.
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// const data = conn.RecvFullHex(1024);
+// ```
+func (c *NetConn) RecvFullHex(N int) (string, error) {
+ bin, err := c.RecvFull(N)
+ if err != nil {
+ return "", err
+ }
+ return hex.Dump(bin), nil
+}
+
+// RecvHex is similar to RecvFullHex but does not guarantee full read instead
+// it creates a buffer of N bytes and returns whatever is returned by the connection
+// for reading headers or initial bytes from the server this is usually used.
+// for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.
+// @example
+// ```javascript
+// const net = require('nuclei/net');
+// const conn = net.Open('tcp', 'acme.com:80');
+// const data = conn.RecvHex(1024);
+// ```
func (c *NetConn) RecvHex(N int) (string, error) {
bin, err := c.Recv(N)
if err != nil {
diff --git a/pkg/js/libs/oracle/memo.oracle.go b/pkg/js/libs/oracle/memo.oracle.go
new file mode 100755
index 0000000000..451f2f642b
--- /dev/null
+++ b/pkg/js/libs/oracle/memo.oracle.go
@@ -0,0 +1,25 @@
+// Warning - This is generated code
+package oracle
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedisOracle(host string, port int) (IsOracleResponse, error) {
+ hash := "isOracle" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return isOracle(host, port)
+ })
+ if err != nil {
+ return IsOracleResponse{}, err
+ }
+ if value, ok := v.(IsOracleResponse); ok {
+ return value, nil
+ }
+
+ return IsOracleResponse{}, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/oracle/oracle.go b/pkg/js/libs/oracle/oracle.go
index d6bb48d1cb..9e43264213 100644
--- a/pkg/js/libs/oracle/oracle.go
+++ b/pkg/js/libs/oracle/oracle.go
@@ -11,17 +11,33 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
-// OracleClient is a minimal Oracle client for nuclei scripts.
-type OracleClient struct{}
+type (
+ // IsOracleResponse is the response from the IsOracle function.
+ // this is returned by IsOracle function.
+ // @example
+ // ```javascript
+ // const oracle = require('nuclei/oracle');
+ // const isOracle = oracle.IsOracle('acme.com', 1521);
+ // ```
+ IsOracleResponse struct {
+ IsOracle bool
+ Banner string
+ }
+)
-// 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
+// @example
+// ```javascript
+// const oracle = require('nuclei/oracle');
+// const isOracle = oracle.IsOracle('acme.com', 1521);
+// log(toJSON(isOracle));
+// ```
+func IsOracle(host string, port int) (IsOracleResponse, error) {
+ return memoizedisOracle(host, port)
}
-// IsOracle checks if a host is running an Oracle server.
-func (c *OracleClient) IsOracle(host string, port int) (IsOracleResponse, error) {
+// @memo
+func isOracle(host string, port int) (IsOracleResponse, error) {
resp := IsOracleResponse{}
timeout := 5 * time.Second
diff --git a/pkg/js/libs/pop3/memo.pop3.go b/pkg/js/libs/pop3/memo.pop3.go
new file mode 100755
index 0000000000..dbd5e46320
--- /dev/null
+++ b/pkg/js/libs/pop3/memo.pop3.go
@@ -0,0 +1,25 @@
+// Warning - This is generated code
+package pop3
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedisPoP3(host string, port int) (IsPOP3Response, error) {
+ hash := "isPoP3" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return isPoP3(host, port)
+ })
+ if err != nil {
+ return IsPOP3Response{}, err
+ }
+ if value, ok := v.(IsPOP3Response); ok {
+ return value, nil
+ }
+
+ return IsPOP3Response{}, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/pop3/pop3.go b/pkg/js/libs/pop3/pop3.go
index f1ff7bb561..2662befd48 100644
--- a/pkg/js/libs/pop3/pop3.go
+++ b/pkg/js/libs/pop3/pop3.go
@@ -11,17 +11,34 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
-// Pop3Client is a minimal POP3 client for nuclei scripts.
-type Pop3Client struct{}
+type (
+ // IsPOP3Response is the response from the IsPOP3 function.
+ // this is returned by IsPOP3 function.
+ // @example
+ // ```javascript
+ // const pop3 = require('nuclei/pop3');
+ // const isPOP3 = pop3.IsPOP3('acme.com', 110);
+ // log(toJSON(isPOP3));
+ // ```
+ IsPOP3Response struct {
+ IsPOP3 bool
+ Banner string
+ }
+)
-// 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.
+// @example
+// ```javascript
+// const pop3 = require('nuclei/pop3');
+// const isPOP3 = pop3.IsPOP3('acme.com', 110);
+// log(toJSON(isPOP3));
+// ```
+func IsPOP3(host string, port int) (IsPOP3Response, error) {
+ return memoizedisPoP3(host, port)
}
-// IsPOP3 checks if a host is running a POP3 server.
-func (c *Pop3Client) IsPOP3(host string, port int) (IsPOP3Response, error) {
+// @memo
+func isPoP3(host string, port int) (IsPOP3Response, error) {
resp := IsPOP3Response{}
timeout := 5 * time.Second
diff --git a/pkg/js/libs/postgres/memo.postgres.go b/pkg/js/libs/postgres/memo.postgres.go
new file mode 100755
index 0000000000..786d87133e
--- /dev/null
+++ b/pkg/js/libs/postgres/memo.postgres.go
@@ -0,0 +1,61 @@
+// Warning - This is generated code
+package postgres
+
+import (
+ "errors"
+ "fmt"
+
+ _ "github.com/lib/pq"
+
+ utils "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedisPostgres(host string, port int) (bool, error) {
+ hash := "isPostgres" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return isPostgres(host, port)
+ })
+ if err != nil {
+ return false, err
+ }
+ if value, ok := v.(bool); ok {
+ return value, nil
+ }
+
+ return false, errors.New("could not convert cached result")
+}
+
+func memoizedexecuteQuery(host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
+ hash := "executeQuery" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName) + ":" + fmt.Sprint(query)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return executeQuery(host, port, username, password, dbName, query)
+ })
+ if err != nil {
+ return nil, err
+ }
+ if value, ok := v.(*utils.SQLResult); ok {
+ return value, nil
+ }
+
+ return nil, errors.New("could not convert cached result")
+}
+
+func memoizedconnect(host string, port int, username string, password string, dbName string) (bool, error) {
+ hash := "connect" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return connect(host, port, username, password, dbName)
+ })
+ if err != nil {
+ return false, err
+ }
+ if value, ok := v.(bool); ok {
+ return value, nil
+ }
+
+ return false, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/postgres/postgres.go b/pkg/js/libs/postgres/postgres.go
index e3c21bf609..3b52604f39 100644
--- a/pkg/js/libs/postgres/postgres.go
+++ b/pkg/js/libs/postgres/postgres.go
@@ -16,16 +16,31 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
-// PGClient is a client for Postgres database.
-//
-// Internally client uses go-pg/pg driver.
-type PGClient struct{}
+type (
+ // PGClient is a client for Postgres database.
+ // Internally client uses go-pg/pg driver.
+ // @example
+ // ```javascript
+ // const postgres = require('nuclei/postgres');
+ // const client = new postgres.PGClient;
+ // ```
+ 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.
+// @example
+// ```javascript
+// const postgres = require('nuclei/postgres');
+// const isPostgres = postgres.IsPostgres('acme.com', 5432);
+// ```
func (c *PGClient) IsPostgres(host string, port int) (bool, error) {
+ return memoizedisPostgres(host, port)
+}
+
+// @memo
+func 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))
@@ -48,18 +63,35 @@ func (c *PGClient) IsPostgres(host string, port int) (bool, error) {
}
// 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.
+// @example
+// ```javascript
+// const postgres = require('nuclei/postgres');
+// const client = new postgres.PGClient;
+// const connected = client.Connect('acme.com', 5432, 'username', 'password');
+// ```
func (c *PGClient) Connect(host string, port int, username, password string) (bool, error) {
- return connect(host, port, username, password, "postgres")
+ return memoizedconnect(host, port, username, password, "postgres")
}
// ExecuteQuery connects to Postgres database using given credentials and database name.
// and executes a query on the db.
+// If connection is successful, it returns the result of the query.
+// @example
+// ```javascript
+// const postgres = require('nuclei/postgres');
+// const client = new postgres.PGClient;
+// const result = client.ExecuteQuery('acme.com', 5432, 'username', 'password', 'dbname', 'select * from users');
+// log(to_json(result));
+// ```
func (c *PGClient) ExecuteQuery(host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {
+ return memoizedexecuteQuery(host, port, username, password, dbName, query)
+}
+
+// @memo
+func executeQuery(host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
if !protocolstate.IsHostAllowed(host) {
// host is not valid according to network policy
return nil, protocolstate.ErrHostDenied.Msgf(host)
@@ -85,16 +117,21 @@ func (c *PGClient) ExecuteQuery(host string, port int, username, password, dbNam
}
// 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.
+// @example
+// ```javascript
+// const postgres = require('nuclei/postgres');
+// const client = new postgres.PGClient;
+// const connected = client.ConnectWithDB('acme.com', 5432, 'username', 'password', 'dbname');
+// ```
func (c *PGClient) ConnectWithDB(host string, port int, username, password, dbName string) (bool, error) {
- return connect(host, port, username, password, dbName)
+ return memoizedconnect(host, port, username, password, dbName)
}
-func connect(host string, port int, username, password, dbName string) (bool, error) {
+// @memo
+func connect(host string, port int, username string, password string, dbName string) (bool, error) {
if host == "" || port <= 0 {
return false, fmt.Errorf("invalid host or port")
}
@@ -112,6 +149,8 @@ func connect(host string, port int, username, password, dbName string) (bool, er
Password: password,
Database: dbName,
})
+ defer db.Close()
+
_, err := db.Exec("select 1")
if err != nil {
switch true {
diff --git a/pkg/js/libs/rdp/memo.rdp.go b/pkg/js/libs/rdp/memo.rdp.go
new file mode 100755
index 0000000000..c592e20e19
--- /dev/null
+++ b/pkg/js/libs/rdp/memo.rdp.go
@@ -0,0 +1,41 @@
+// Warning - This is generated code
+package rdp
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedisRDP(host string, port int) (IsRDPResponse, error) {
+ hash := "isRDP" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return isRDP(host, port)
+ })
+ if err != nil {
+ return IsRDPResponse{}, err
+ }
+ if value, ok := v.(IsRDPResponse); ok {
+ return value, nil
+ }
+
+ return IsRDPResponse{}, errors.New("could not convert cached result")
+}
+
+func memoizedcheckRDPAuth(host string, port int) (CheckRDPAuthResponse, error) {
+ hash := "checkRDPAuth" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return checkRDPAuth(host, port)
+ })
+ if err != nil {
+ return CheckRDPAuthResponse{}, err
+ }
+ if value, ok := v.(CheckRDPAuthResponse); ok {
+ return value, nil
+ }
+
+ return CheckRDPAuthResponse{}, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/rdp/rdp.go b/pkg/js/libs/rdp/rdp.go
index ff64b63e96..7a985ab25a 100644
--- a/pkg/js/libs/rdp/rdp.go
+++ b/pkg/js/libs/rdp/rdp.go
@@ -10,21 +10,37 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
-// RDPClient is a client for rdp servers
-type RDPClient struct{}
-
-type IsRDPResponse struct {
- IsRDP bool
- OS string
-}
+type (
+ // IsRDPResponse is the response from the IsRDP function.
+ // this is returned by IsRDP function.
+ // @example
+ // ```javascript
+ // const rdp = require('nuclei/rdp');
+ // const isRDP = rdp.IsRDP('acme.com', 3389);
+ // log(toJSON(isRDP));
+ // ```
+ 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) {
+// @example
+// ```javascript
+// const rdp = require('nuclei/rdp');
+// const isRDP = rdp.IsRDP('acme.com', 3389);
+// log(toJSON(isRDP));
+// ```
+func IsRDP(host string, port int) (IsRDPResponse, error) {
+ return memoizedisRDP(host, port)
+}
+
+// @memo
+func isRDP(host string, port int) (IsRDPResponse, error) {
resp := IsRDPResponse{}
timeout := 5 * time.Second
@@ -46,14 +62,36 @@ func (c *RDPClient) IsRDP(host string, port int) (IsRDPResponse, error) {
return resp, nil
}
-type CheckRDPAuthResponse struct {
- PluginInfo *plugins.ServiceRDP
- Auth bool
-}
+type (
+ // CheckRDPAuthResponse is the response from the CheckRDPAuth function.
+ // this is returned by CheckRDPAuth function.
+ // @example
+ // ```javascript
+ // const rdp = require('nuclei/rdp');
+ // const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
+ // log(toJSON(checkRDPAuth));
+ // ```
+ 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) {
+// If connection is successful, it returns true.
+// @example
+// ```javascript
+// const rdp = require('nuclei/rdp');
+// const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
+// log(toJSON(checkRDPAuth));
+// ```
+func CheckRDPAuth(host string, port int) (CheckRDPAuthResponse, error) {
+ return memoizedcheckRDPAuth(host, port)
+}
+
+// @memo
+func checkRDPAuth(host string, port int) (CheckRDPAuthResponse, error) {
resp := CheckRDPAuthResponse{}
timeout := 5 * time.Second
diff --git a/pkg/js/libs/redis/memo.redis.go b/pkg/js/libs/redis/memo.redis.go
new file mode 100755
index 0000000000..d53c448938
--- /dev/null
+++ b/pkg/js/libs/redis/memo.redis.go
@@ -0,0 +1,73 @@
+// Warning - This is generated code
+package redis
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedgetServerInfo(host string, port int) (string, error) {
+ hash := "getServerInfo" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return getServerInfo(host, port)
+ })
+ if err != nil {
+ return "", err
+ }
+ if value, ok := v.(string); ok {
+ return value, nil
+ }
+
+ return "", errors.New("could not convert cached result")
+}
+
+func memoizedconnect(host string, port int, password string) (bool, error) {
+ hash := "connect" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return connect(host, port, password)
+ })
+ if err != nil {
+ return false, err
+ }
+ if value, ok := v.(bool); ok {
+ return value, nil
+ }
+
+ return false, errors.New("could not convert cached result")
+}
+
+func memoizedgetServerInfoAuth(host string, port int, password string) (string, error) {
+ hash := "getServerInfoAuth" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return getServerInfoAuth(host, port, password)
+ })
+ if err != nil {
+ return "", err
+ }
+ if value, ok := v.(string); ok {
+ return value, nil
+ }
+
+ return "", errors.New("could not convert cached result")
+}
+
+func memoizedisAuthenticated(host string, port int) (bool, error) {
+ hash := "isAuthenticated" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return isAuthenticated(host, port)
+ })
+ if err != nil {
+ return false, err
+ }
+ if value, ok := v.(bool); ok {
+ return value, nil
+ }
+
+ return false, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/redis/redis.go b/pkg/js/libs/redis/redis.go
index 686f9ff780..3fb4de83a6 100644
--- a/pkg/js/libs/redis/redis.go
+++ b/pkg/js/libs/redis/redis.go
@@ -13,7 +13,17 @@ import (
)
// GetServerInfo returns the server info for a redis server
+// @example
+// ```javascript
+// const redis = require('nuclei/redis');
+// const info = redis.GetServerInfo('acme.com', 6379);
+// ```
func GetServerInfo(host string, port int) (string, error) {
+ return memoizedgetServerInfo(host, port)
+}
+
+// @memo
+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)
@@ -24,6 +34,7 @@ func GetServerInfo(host string, port int) (string, error) {
Password: "", // no password set
DB: 0, // use default DB
})
+ defer client.Close()
// Ping the Redis server
_, err := client.Ping(context.TODO()).Result()
@@ -41,7 +52,17 @@ func GetServerInfo(host string, port int) (string, error) {
}
// Connect tries to connect redis server with password
+// @example
+// ```javascript
+// const redis = require('nuclei/redis');
+// const connected = redis.Connect('acme.com', 6379, 'password');
+// ```
func Connect(host string, port int, password string) (bool, error) {
+ return memoizedconnect(host, port, password)
+}
+
+// @memo
+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)
@@ -52,6 +73,8 @@ func Connect(host string, port int, password string) (bool, error) {
Password: password, // no password set
DB: 0, // use default DB
})
+ defer client.Close()
+
_, err := client.Ping(context.TODO()).Result()
if err != nil {
return false, err
@@ -66,7 +89,17 @@ func Connect(host string, port int, password string) (bool, error) {
}
// GetServerInfoAuth returns the server info for a redis server
+// @example
+// ```javascript
+// const redis = require('nuclei/redis');
+// const info = redis.GetServerInfoAuth('acme.com', 6379, 'password');
+// ```
func GetServerInfoAuth(host string, port int, password string) (string, error) {
+ return memoizedgetServerInfoAuth(host, port, password)
+}
+
+// @memo
+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)
@@ -77,6 +110,7 @@ func GetServerInfoAuth(host string, port int, password string) (string, error) {
Password: password, // no password set
DB: 0, // use default DB
})
+ defer client.Close()
// Ping the Redis server
_, err := client.Ping(context.TODO()).Result()
@@ -94,7 +128,17 @@ func GetServerInfoAuth(host string, port int, password string) (string, error) {
}
// IsAuthenticated checks if the redis server requires authentication
+// @example
+// ```javascript
+// const redis = require('nuclei/redis');
+// const isAuthenticated = redis.IsAuthenticated('acme.com', 6379);
+// ```
func IsAuthenticated(host string, port int) (bool, error) {
+ return memoizedisAuthenticated(host, port)
+}
+
+// @memo
+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))
@@ -110,7 +154,12 @@ func IsAuthenticated(host string, port int) (bool, error) {
return true, nil
}
-// RunLuaScript runs a lua script on
+// RunLuaScript runs a lua script on the redis server
+// @example
+// ```javascript
+// const redis = require('nuclei/redis');
+// const result = redis.RunLuaScript('acme.com', 6379, 'password', 'return redis.call("get", KEYS[1])');
+// ```
func RunLuaScript(host string, port int, password string, script string) (interface{}, error) {
if !protocolstate.IsHostAllowed(host) {
// host is not valid according to network policy
@@ -122,6 +171,7 @@ func RunLuaScript(host string, port int, password string, script string) (interf
Password: password,
DB: 0, // use default DB
})
+ defer client.Close()
// Ping the Redis server
_, err := client.Ping(context.TODO()).Result()
diff --git a/pkg/js/libs/rsync/memo.rsync.go b/pkg/js/libs/rsync/memo.rsync.go
new file mode 100755
index 0000000000..5cb0d02979
--- /dev/null
+++ b/pkg/js/libs/rsync/memo.rsync.go
@@ -0,0 +1,25 @@
+// Warning - This is generated code
+package rsync
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedisRsync(host string, port int) (IsRsyncResponse, error) {
+ hash := "isRsync" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return isRsync(host, port)
+ })
+ if err != nil {
+ return IsRsyncResponse{}, err
+ }
+ if value, ok := v.(IsRsyncResponse); ok {
+ return value, nil
+ }
+
+ return IsRsyncResponse{}, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/rsync/rsync.go b/pkg/js/libs/rsync/rsync.go
index 267c1d5c76..c9cf18f378 100644
--- a/pkg/js/libs/rsync/rsync.go
+++ b/pkg/js/libs/rsync/rsync.go
@@ -11,17 +11,34 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
-// RsyncClient is a minimal Rsync client for nuclei scripts.
-type RsyncClient struct{}
+type (
+ // IsRsyncResponse is the response from the IsRsync function.
+ // this is returned by IsRsync function.
+ // @example
+ // ```javascript
+ // const rsync = require('nuclei/rsync');
+ // const isRsync = rsync.IsRsync('acme.com', 873);
+ // log(toJSON(isRsync));
+ // ```
+ IsRsyncResponse struct {
+ IsRsync bool
+ Banner string
+ }
+)
-// 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.
+// @example
+// ```javascript
+// const rsync = require('nuclei/rsync');
+// const isRsync = rsync.IsRsync('acme.com', 873);
+// log(toJSON(isRsync));
+// ```
+func IsRsync(host string, port int) (IsRsyncResponse, error) {
+ return memoizedisRsync(host, port)
}
-// IsRsync checks if a host is running a Rsync server.
-func (c *RsyncClient) IsRsync(host string, port int) (IsRsyncResponse, error) {
+// @memo
+func isRsync(host string, port int) (IsRsyncResponse, error) {
resp := IsRsyncResponse{}
timeout := 5 * time.Second
diff --git a/pkg/js/libs/smb/memo.smb.go b/pkg/js/libs/smb/memo.smb.go
new file mode 100755
index 0000000000..51d6584f0e
--- /dev/null
+++ b/pkg/js/libs/smb/memo.smb.go
@@ -0,0 +1,43 @@
+// Warning - This is generated code
+package smb
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+
+ "github.com/zmap/zgrab2/lib/smb/smb"
+)
+
+func memoizedconnectSMBInfoMode(host string, port int) (*smb.SMBLog, error) {
+ hash := "connectSMBInfoMode" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return connectSMBInfoMode(host, port)
+ })
+ if err != nil {
+ return nil, err
+ }
+ if value, ok := v.(*smb.SMBLog); ok {
+ return value, nil
+ }
+
+ return nil, errors.New("could not convert cached result")
+}
+
+func memoizedlistShares(host string, port int, user string, password string) ([]string, error) {
+ hash := "listShares" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(user) + ":" + fmt.Sprint(password)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return listShares(host, port, user, password)
+ })
+ if err != nil {
+ return []string{}, err
+ }
+ if value, ok := v.([]string); ok {
+ return value, nil
+ }
+
+ return []string{}, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/smb/memo.smb_private.go b/pkg/js/libs/smb/memo.smb_private.go
new file mode 100755
index 0000000000..fe47d1a284
--- /dev/null
+++ b/pkg/js/libs/smb/memo.smb_private.go
@@ -0,0 +1,29 @@
+// Warning - This is generated code
+package smb
+
+import (
+ "errors"
+ "fmt"
+
+ "time"
+
+ "github.com/praetorian-inc/fingerprintx/pkg/plugins"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedcollectSMBv2Metadata(host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) {
+ hash := "collectSMBv2Metadata" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(timeout)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return collectSMBv2Metadata(host, port, timeout)
+ })
+ if err != nil {
+ return nil, err
+ }
+ if value, ok := v.(*plugins.ServiceSMB); ok {
+ return value, nil
+ }
+
+ return nil, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/smb/memo.smbghost.go b/pkg/js/libs/smb/memo.smbghost.go
new file mode 100755
index 0000000000..25e9d1878a
--- /dev/null
+++ b/pkg/js/libs/smb/memo.smbghost.go
@@ -0,0 +1,25 @@
+// Warning - This is generated code
+package smb
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizeddetectSMBGhost(host string, port int) (bool, error) {
+ hash := "detectSMBGhost" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return detectSMBGhost(host, port)
+ })
+ if err != nil {
+ return false, err
+ }
+ if value, ok := v.(bool); ok {
+ return value, nil
+ }
+
+ return false, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/smb/smb.go b/pkg/js/libs/smb/smb.go
index df9d28194b..2d98128147 100644
--- a/pkg/js/libs/smb/smb.go
+++ b/pkg/js/libs/smb/smb.go
@@ -11,18 +11,35 @@ import (
"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/projectdiscovery/go-smb2 driver
-type SMBClient struct{}
+type (
+ // SMBClient is a client for SMB servers.
+ // Internally client uses github.com/zmap/zgrab2/lib/smb/smb driver.
+ // github.com/projectdiscovery/go-smb2 driver
+ // @example
+ // ```javascript
+ // const smb = require('nuclei/smb');
+ // const client = new smb.SMBClient();
+ // ```
+ 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
+// @example
+// ```javascript
+// const smb = require('nuclei/smb');
+// const client = new smb.SMBClient();
+// const info = client.ConnectSMBInfoMode('acme.com', 445);
+// log(to_json(info));
+// ```
func (c *SMBClient) ConnectSMBInfoMode(host string, port int) (*smb.SMBLog, error) {
+ return memoizedconnectSMBInfoMode(host, port)
+}
+
+// @memo
+func connectSMBInfoMode(host string, port int) (*smb.SMBLog, error) {
if !protocolstate.IsHostAllowed(host) {
// host is not valid according to network policy
return nil, protocolstate.ErrHostDenied.Msgf(host)
@@ -32,7 +49,7 @@ func (c *SMBClient) ConnectSMBInfoMode(host string, port int) (*smb.SMBLog, erro
return nil, err
}
// try to get SMBv2/v3 info
- result, err := c.getSMBInfo(conn, true, false)
+ result, err := getSMBInfo(conn, true, false)
_ = conn.Close() // close regardless of error
if err == nil {
return result, nil
@@ -44,7 +61,7 @@ func (c *SMBClient) ConnectSMBInfoMode(host string, port int) (*smb.SMBLog, erro
return nil, err
}
defer conn.Close()
- result, err = c.getSMBInfo(conn, true, true)
+ result, err = getSMBInfo(conn, true, true)
if err != nil {
return result, nil
}
@@ -53,23 +70,44 @@ func (c *SMBClient) ConnectSMBInfoMode(host string, port int) (*smb.SMBLog, erro
// 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
+// @example
+// ```javascript
+// const smb = require('nuclei/smb');
+// const client = new smb.SMBClient();
+// const metadata = client.ListSMBv2Metadata('acme.com', 445);
+// log(to_json(metadata));
+// ```
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)
+ return memoizedcollectSMBv2Metadata(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.
+// @example
+// ```javascript
+// const smb = require('nuclei/smb');
+// const client = new smb.SMBClient();
+// const shares = client.ListShares('acme.com', 445, 'username', 'password');
+//
+// for (const share of shares) {
+// log(share);
+// }
+//
+// ```
func (c *SMBClient) ListShares(host string, port int, user, password string) ([]string, error) {
+ return memoizedlistShares(host, port, user, password)
+}
+
+// @memo
+func listShares(host string, port int, user string, password string) ([]string, error) {
if !protocolstate.IsHostAllowed(host) {
// host is not valid according to network policy
return nil, protocolstate.ErrHostDenied.Msgf(host)
diff --git a/pkg/js/libs/smb/smb_private.go b/pkg/js/libs/smb/smb_private.go
index 6d766f0ebe..74f076b12a 100644
--- a/pkg/js/libs/smb/smb_private.go
+++ b/pkg/js/libs/smb/smb_private.go
@@ -15,6 +15,7 @@ import (
// ==== private helper functions/methods ====
// collectSMBv2Metadata collects metadata for SMBv2 services.
+// @memo
func collectSMBv2Metadata(host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) {
if timeout == 0 {
timeout = 5 * time.Second
@@ -33,7 +34,7 @@ func collectSMBv2Metadata(host string, port int, timeout time.Duration) (*plugin
}
// getSMBInfo
-func (c *SMBClient) getSMBInfo(conn net.Conn, setupSession, v1 bool) (*zgrabsmb.SMBLog, error) {
+func getSMBInfo(conn net.Conn, setupSession, v1 bool) (*zgrabsmb.SMBLog, error) {
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
defer func() {
_ = conn.SetDeadline(time.Time{})
diff --git a/pkg/js/libs/smb/smbghost.go b/pkg/js/libs/smb/smbghost.go
index 58f210fe4d..6c8b3c27c1 100644
--- a/pkg/js/libs/smb/smbghost.go
+++ b/pkg/js/libs/smb/smbghost.go
@@ -19,7 +19,18 @@ const (
// DetectSMBGhost tries to detect SMBGhost vulnerability
// by using SMBv3 compression feature.
+// If the host is vulnerable, it returns true.
+// @example
+// ```javascript
+// const smb = require('nuclei/smb');
+// const isSMBGhost = smb.DetectSMBGhost('acme.com', 445);
+// ```
func (c *SMBClient) DetectSMBGhost(host string, port int) (bool, error) {
+ return memoizeddetectSMBGhost(host, port)
+}
+
+// @memo
+func detectSMBGhost(host string, port int) (bool, error) {
if !protocolstate.IsHostAllowed(host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
diff --git a/pkg/js/libs/smtp/msg.go b/pkg/js/libs/smtp/msg.go
index 58d263c179..ceabbfea68 100644
--- a/pkg/js/libs/smtp/msg.go
+++ b/pkg/js/libs/smtp/msg.go
@@ -7,41 +7,79 @@ import (
"strings"
)
-// SMTPMessage is a simple smtp message builder
-type SMTPMessage struct {
- from string
- to []string
- sub string
- msg []byte
- user string
- pass string
-}
+type (
+ // SMTPMessage is a message to be sent over SMTP
+ // @example
+ // ```javascript
+ // const smtp = require('nuclei/smtp');
+ // const message = new smtp.SMTPMessage();
+ // message.From('xyz@projectdiscovery.io');
+ // ```
+ SMTPMessage struct {
+ from string
+ to []string
+ sub string
+ msg []byte
+ user string
+ pass string
+ }
+)
// From adds the from field to the message
+// @example
+// ```javascript
+// const smtp = require('nuclei/smtp');
+// const message = new smtp.SMTPMessage();
+// message.From('xyz@projectdiscovery.io');
+// ```
func (s *SMTPMessage) From(email string) *SMTPMessage {
s.from = email
return s
}
// To adds the to field to the message
+// @example
+// ```javascript
+// const smtp = require('nuclei/smtp');
+// const message = new smtp.SMTPMessage();
+// message.To('xyz@projectdiscovery.io');
+// ```
func (s *SMTPMessage) To(email string) *SMTPMessage {
s.to = append(s.to, email)
return s
}
// Subject adds the subject field to the message
+// @example
+// ```javascript
+// const smtp = require('nuclei/smtp');
+// const message = new smtp.SMTPMessage();
+// message.Subject('hello');
+// ```
func (s *SMTPMessage) Subject(sub string) *SMTPMessage {
s.sub = sub
return s
}
// Body adds the message body to the message
+// @example
+// ```javascript
+// const smtp = require('nuclei/smtp');
+// const message = new smtp.SMTPMessage();
+// message.Body('hello');
+// ```
func (s *SMTPMessage) Body(msg []byte) *SMTPMessage {
s.msg = msg
return s
}
// Auth when called authenticates using username and password before sending the message
+// @example
+// ```javascript
+// const smtp = require('nuclei/smtp');
+// const message = new smtp.SMTPMessage();
+// message.Auth('username', 'password');
+// ```
func (s *SMTPMessage) Auth(username, password string) *SMTPMessage {
s.user = username
s.pass = password
@@ -49,6 +87,16 @@ func (s *SMTPMessage) Auth(username, password string) *SMTPMessage {
}
// String returns the string representation of the message
+// @example
+// ```javascript
+// const smtp = require('nuclei/smtp');
+// const message = new smtp.SMTPMessage();
+// message.From('xyz@projectdiscovery.io');
+// message.To('xyz2@projectdiscoveyr.io');
+// message.Subject('hello');
+// message.Body('hello');
+// log(message.String());
+// ```
func (s *SMTPMessage) String() string {
var buff bytes.Buffer
tw := textproto.NewWriter(bufio.NewWriter(&buff))
diff --git a/pkg/js/libs/smtp/smtp.go b/pkg/js/libs/smtp/smtp.go
index 11659076e4..7fe9076e22 100644
--- a/pkg/js/libs/smtp/smtp.go
+++ b/pkg/js/libs/smtp/smtp.go
@@ -8,34 +8,92 @@ import (
"strconv"
"time"
+ "github.com/dop251/goja"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
+ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
pluginsmtp "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/smtp"
)
-// SMTPClient is a minimal SMTP client for nuclei scripts.
-type SMTPClient struct{}
+type (
+ // SMTPResponse is the response from the IsSMTP function.
+ // @example
+ // ```javascript
+ // const smtp = require('nuclei/smtp');
+ // const client = new smtp.Client('acme.com', 25);
+ // const isSMTP = client.IsSMTP();
+ // log(isSMTP)
+ // ```
+ SMTPResponse struct {
+ IsSMTP bool
+ Banner string
+ }
+)
+
+type (
+ // Client is a minimal SMTP client for nuclei scripts.
+ // @example
+ // ```javascript
+ // const smtp = require('nuclei/smtp');
+ // const client = new smtp.Client('acme.com', 25);
+ // ```
+ Client struct {
+ nj *utils.NucleiJS
+ host string
+ port string
+ }
+)
+
+// Constructor for SMTP Client
+// Constructor: constructor(public host: string, public port: string)
+func NewSMTPClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
+ // setup nucleijs utils
+ c := &Client{nj: utils.NewNucleiJS(runtime)}
+ c.nj.ObjectSig = "Client(host, port)" // will be included in error messages
+
+ host, _ := c.nj.GetArg(call.Arguments, 0).(string) // host
+ port, _ := c.nj.GetArg(call.Arguments, 1).(string) // port
-// IsSMTPResponse is the response from the IsSMTP function.
-type IsSMTPResponse struct {
- IsSMTP bool
- Banner string
+ // validate arguments
+ c.nj.Require(host != "", "host cannot be empty")
+ c.nj.Require(port != "", "port cannot be empty")
+
+ // validate port
+ portInt, err := strconv.Atoi(port)
+ c.nj.Require(err == nil && portInt > 0 && portInt < 65536, "port must be a valid number")
+ c.host = host
+ c.port = port
+
+ // check if this is allowed address
+ c.nj.Require(protocolstate.IsHostAllowed(host+":"+port), protocolstate.ErrHostDenied.Msgf(host+":"+port).Error())
+
+ // Link Constructor to Client and return
+ return utils.LinkConstructor(call, runtime, c)
}
// IsSMTP checks if a host is running a SMTP server.
-func (c *SMTPClient) IsSMTP(host string, port int) (IsSMTPResponse, error) {
- resp := IsSMTPResponse{}
+// @example
+// ```javascript
+// const smtp = require('nuclei/smtp');
+// const client = new smtp.Client('acme.com', 25);
+// const isSMTP = client.IsSMTP();
+// log(isSMTP)
+// ```
+func (c *Client) IsSMTP() (SMTPResponse, error) {
+ resp := SMTPResponse{}
+ c.nj.Require(c.host != "", "host cannot be empty")
+ c.nj.Require(c.port != "", "port cannot be empty")
timeout := 5 * time.Second
- conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
+ conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", net.JoinHostPort(c.host, c.port))
if err != nil {
return resp, err
}
defer conn.Close()
smtpPlugin := pluginsmtp.SMTPPlugin{}
- service, err := smtpPlugin.Run(conn, timeout, plugins.Target{Host: host})
+ service, err := smtpPlugin.Run(conn, timeout, plugins.Target{Host: c.host})
if err != nil {
return resp, err
}
@@ -47,18 +105,29 @@ func (c *SMTPClient) IsSMTP(host string, port int) (IsSMTPResponse, error) {
return resp, nil
}
-func (c *SMTPClient) IsOpenRelay(host string, port int, msg *SMTPMessage) (bool, error) {
- if !protocolstate.IsHostAllowed(host) {
- return false, protocolstate.ErrHostDenied.Msgf(host)
- }
+// IsOpenRelay checks if a host is an open relay.
+// @example
+// ```javascript
+// const smtp = require('nuclei/smtp');
+// const message = new smtp.SMTPMessage();
+// message.From('xyz@projectdiscovery.io');
+// message.To('xyz2@projectdiscoveyr.io');
+// message.Subject('hello');
+// message.Body('hello');
+// const client = new smtp.Client('acme.com', 25);
+// const isRelay = client.IsOpenRelay(message);
+// ```
+func (c *Client) IsOpenRelay(msg *SMTPMessage) (bool, error) {
+ c.nj.Require(c.host != "", "host cannot be empty")
+ c.nj.Require(c.port != "", "port cannot be empty")
- addr := net.JoinHostPort(host, strconv.Itoa(port))
+ addr := net.JoinHostPort(c.host, c.port)
conn, err := protocolstate.Dialer.Dial(context.TODO(), "tcp", addr)
if err != nil {
return false, err
}
defer conn.Close()
- client, err := smtp.NewClient(conn, host)
+ client, err := smtp.NewClient(conn, c.host)
if err != nil {
return false, err
}
@@ -95,20 +164,31 @@ func (c *SMTPClient) IsOpenRelay(host string, port int, msg *SMTPMessage) (bool,
}
// SendMail sends an email using the SMTP protocol.
-func (c *SMTPClient) SendMail(host string, port string, msg *SMTPMessage) (bool, error) {
- if !protocolstate.IsHostAllowed(host) {
- return false, protocolstate.ErrHostDenied.Msgf(host)
- }
+// @example
+// ```javascript
+// const smtp = require('nuclei/smtp');
+// const message = new smtp.SMTPMessage();
+// message.From('xyz@projectdiscovery.io');
+// message.To('xyz2@projectdiscoveyr.io');
+// message.Subject('hello');
+// message.Body('hello');
+// const client = new smtp.Client('acme.com', 25);
+// const isSent = client.SendMail(message);
+// log(isSent)
+// ```
+func (c *Client) SendMail(msg *SMTPMessage) (bool, error) {
+ c.nj.Require(c.host != "", "host cannot be empty")
+ c.nj.Require(c.port != "", "port cannot be empty")
var auth smtp.Auth
if msg.user != "" && msg.pass != "" {
- auth = smtp.PlainAuth("", msg.user, msg.pass, host)
+ auth = smtp.PlainAuth("", msg.user, msg.pass, c.host)
}
// send mail
- addr := net.JoinHostPort(host, port)
+ addr := net.JoinHostPort(c.host, c.port)
if err := smtp.SendMail(addr, auth, msg.from, msg.to, []byte(msg.String())); err != nil {
- return false, err
+ c.nj.Throw("failed to send mail with message(%s) got %v", msg.String(), err)
}
return true, nil
}
diff --git a/pkg/js/libs/ssh/memo.ssh.go b/pkg/js/libs/ssh/memo.ssh.go
new file mode 100755
index 0000000000..0bfed5849e
--- /dev/null
+++ b/pkg/js/libs/ssh/memo.ssh.go
@@ -0,0 +1,27 @@
+// Warning - This is generated code
+package ssh
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+
+ "github.com/zmap/zgrab2/lib/ssh"
+)
+
+func memoizedconnectSSHInfoMode(opts *connectOptions) (*ssh.HandshakeLog, error) {
+ hash := "connectSSHInfoMode" + ":" + fmt.Sprint(opts)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return connectSSHInfoMode(opts)
+ })
+ if err != nil {
+ return nil, err
+ }
+ if value, ok := v.(*ssh.HandshakeLog); ok {
+ return value, nil
+ }
+
+ return nil, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/ssh/ssh.go b/pkg/js/libs/ssh/ssh.go
index 24945f915e..f6639a57bc 100644
--- a/pkg/js/libs/ssh/ssh.go
+++ b/pkg/js/libs/ssh/ssh.go
@@ -10,24 +10,41 @@ import (
"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 {
- Connection *ssh.Client
- timeout time.Duration
-}
+type (
+ // SSHClient is a client for SSH servers.
+ // Internally client uses github.com/zmap/zgrab2/lib/ssh driver.
+ // @example
+ // ```javascript
+ // const ssh = require('nuclei/ssh');
+ // const client = new ssh.SSHClient();
+ // ```
+ SSHClient struct {
+ connection *ssh.Client
+ timeout time.Duration
+ }
+)
// SetTimeout sets the timeout for the SSH connection in seconds
+// @example
+// ```javascript
+// const ssh = require('nuclei/ssh');
+// const client = new ssh.SSHClient();
+// client.SetTimeout(10);
+// ```
func (c *SSHClient) SetTimeout(sec int) {
c.timeout = time.Duration(sec) * time.Second
}
// 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
+// @example
+// ```javascript
+// const ssh = require('nuclei/ssh');
+// const client = new ssh.SSHClient();
+// const connected = client.Connect('acme.com', 22, 'username', 'password');
+// ```
func (c *SSHClient) Connect(host string, port int, username, password string) (bool, error) {
conn, err := connect(&connectOptions{
Host: host,
@@ -38,16 +55,22 @@ func (c *SSHClient) Connect(host string, port int, username, password string) (b
if err != nil {
return false, err
}
- c.Connection = conn
+ c.connection = conn
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
+// @example
+// ```javascript
+// const ssh = require('nuclei/ssh');
+// const client = new ssh.SSHClient();
+// const privateKey = `-----BEGIN RSA PRIVATE KEY----- ...`;
+// const connected = client.ConnectWithKey('acme.com', 22, 'username', privateKey);
+// ```
func (c *SSHClient) ConnectWithKey(host string, port int, username, key string) (bool, error) {
conn, err := connect(&connectOptions{
Host: host,
@@ -59,21 +82,26 @@ func (c *SSHClient) ConnectWithKey(host string, port int, username, key string)
if err != nil {
return false, err
}
- c.Connection = conn
+ c.connection = conn
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
+// @example
+// ```javascript
+// const ssh = require('nuclei/ssh');
+// const client = new ssh.SSHClient();
+// const info = client.ConnectSSHInfoMode('acme.com', 22);
+// log(to_json(info));
+// ```
func (c *SSHClient) ConnectSSHInfoMode(host string, port int) (*ssh.HandshakeLog, error) {
- return connectSSHInfoMode(&connectOptions{
+ return memoizedconnectSSHInfoMode(&connectOptions{
Host: host,
Port: port,
})
@@ -81,16 +109,22 @@ func (c *SSHClient) ConnectSSHInfoMode(host string, port int) (*ssh.HandshakeLog
// Run tries to open a new SSH session, then tries to execute
// the provided command in said session
-//
// Returns string and error. If error is not nil,
// state will be false
-//
// The string contains the command output
+// @example
+// ```javascript
+// const ssh = require('nuclei/ssh');
+// const client = new ssh.SSHClient();
+// client.Connect('acme.com', 22, 'username', 'password');
+// const output = client.Run('id');
+// log(output);
+// ```
func (c *SSHClient) Run(cmd string) (string, error) {
- if c.Connection == nil {
+ if c.connection == nil {
return "", errorutil.New("no connection")
}
- session, err := c.Connection.NewSession()
+ session, err := c.connection.NewSession()
if err != nil {
return "", err
}
@@ -105,11 +139,17 @@ func (c *SSHClient) Run(cmd string) (string, error) {
}
// Close closes the SSH connection and destroys the client
-//
// Returns the success state and error. If error is not nil,
// state will be false
+// @example
+// ```javascript
+// const ssh = require('nuclei/ssh');
+// const client = new ssh.SSHClient();
+// client.Connect('acme.com', 22, 'username', 'password');
+// const closed = client.Close();
+// ```
func (c *SSHClient) Close() (bool, error) {
- if err := c.Connection.Close(); err != nil {
+ if err := c.connection.Close(); err != nil {
return false, err
}
return true, nil
@@ -142,6 +182,7 @@ func (c *connectOptions) validate() error {
return nil
}
+// @memo
func connectSSHInfoMode(opts *connectOptions) (*ssh.HandshakeLog, error) {
if err := opts.validate(); err != nil {
return nil, err
diff --git a/pkg/js/libs/structs/structs.go b/pkg/js/libs/structs/structs.go
index 5e66d786df..650d2a3d7b 100644
--- a/pkg/js/libs/structs/structs.go
+++ b/pkg/js/libs/structs/structs.go
@@ -11,6 +11,11 @@ import (
// 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])
+// @example
+// ```javascript
+// const structs = require('nuclei/structs');
+// const result = structs.Unpack('H', [0]);
+// ```
func Unpack(format string, msg []byte) ([]interface{}, error) {
return gostruct.UnPack(buildFormatSliceFromStringFormat(format), msg)
}
@@ -18,6 +23,11 @@ func Unpack(format string, msg []byte) ([]interface{}, error) {
// 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)
+// @example
+// ```javascript
+// const structs = require('nuclei/structs');
+// const packed = structs.Pack('H', [0]);
+// ```
func Pack(formatStr string, msg interface{}) ([]byte, error) {
var args []interface{}
switch v := msg.(type) {
@@ -44,6 +54,12 @@ func Pack(formatStr string, msg interface{}) ([]byte, error) {
}
// StructsCalcSize returns the number of bytes needed to pack the values according to the given format.
+// Ex: structs.CalcSize("H")
+// @example
+// ```javascript
+// const structs = require('nuclei/structs');
+// const size = structs.CalcSize('H');
+// ```
func StructsCalcSize(format string) (int, error) {
return gostruct.CalcSize(buildFormatSliceFromStringFormat(format))
}
diff --git a/pkg/js/libs/telnet/memo.telnet.go b/pkg/js/libs/telnet/memo.telnet.go
new file mode 100755
index 0000000000..0e29a5e732
--- /dev/null
+++ b/pkg/js/libs/telnet/memo.telnet.go
@@ -0,0 +1,25 @@
+// Warning - This is generated code
+package telnet
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedisTelnet(host string, port int) (IsTelnetResponse, error) {
+ hash := "isTelnet" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return isTelnet(host, port)
+ })
+ if err != nil {
+ return IsTelnetResponse{}, err
+ }
+ if value, ok := v.(IsTelnetResponse); ok {
+ return value, nil
+ }
+
+ return IsTelnetResponse{}, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/telnet/telnet.go b/pkg/js/libs/telnet/telnet.go
index 5cf58a51c6..d714547549 100644
--- a/pkg/js/libs/telnet/telnet.go
+++ b/pkg/js/libs/telnet/telnet.go
@@ -11,17 +11,34 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
-// TelnetClient is a minimal Telnet client for nuclei scripts.
-type TelnetClient struct{}
+type (
+ // IsTelnetResponse is the response from the IsTelnet function.
+ // this is returned by IsTelnet function.
+ // @example
+ // ```javascript
+ // const telnet = require('nuclei/telnet');
+ // const isTelnet = telnet.IsTelnet('acme.com', 23);
+ // log(toJSON(isTelnet));
+ // ```
+ IsTelnetResponse struct {
+ IsTelnet bool
+ Banner string
+ }
+)
-// 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.
+// @example
+// ```javascript
+// const telnet = require('nuclei/telnet');
+// const isTelnet = telnet.IsTelnet('acme.com', 23);
+// log(toJSON(isTelnet));
+// ```
+func IsTelnet(host string, port int) (IsTelnetResponse, error) {
+ return memoizedisTelnet(host, port)
}
-// IsTelnet checks if a host is running a Telnet server.
-func (c *TelnetClient) IsTelnet(host string, port int) (IsTelnetResponse, error) {
+// @memo
+func isTelnet(host string, port int) (IsTelnetResponse, error) {
resp := IsTelnetResponse{}
timeout := 5 * time.Second
diff --git a/pkg/js/libs/vnc/memo.vnc.go b/pkg/js/libs/vnc/memo.vnc.go
new file mode 100755
index 0000000000..8e2fd4546c
--- /dev/null
+++ b/pkg/js/libs/vnc/memo.vnc.go
@@ -0,0 +1,25 @@
+// Warning - This is generated code
+package vnc
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
+)
+
+func memoizedisVNC(host string, port int) (IsVNCResponse, error) {
+ hash := "isVNC" + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
+
+ v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
+ return isVNC(host, port)
+ })
+ if err != nil {
+ return IsVNCResponse{}, err
+ }
+ if value, ok := v.(IsVNCResponse); ok {
+ return value, nil
+ }
+
+ return IsVNCResponse{}, errors.New("could not convert cached result")
+}
diff --git a/pkg/js/libs/vnc/vnc.go b/pkg/js/libs/vnc/vnc.go
index e5857580bf..c5d4577c08 100644
--- a/pkg/js/libs/vnc/vnc.go
+++ b/pkg/js/libs/vnc/vnc.go
@@ -11,19 +11,35 @@ import (
"github.com/projectdiscovery/nuclei/v3/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
-}
+type (
+ // IsVNCResponse is the response from the IsVNC function.
+ // @example
+ // ```javascript
+ // const vnc = require('nuclei/vnc');
+ // const isVNC = vnc.IsVNC('acme.com', 5900);
+ // log(toJSON(isVNC));
+ // ```
+ 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) {
+// @example
+// ```javascript
+// const vnc = require('nuclei/vnc');
+// const isVNC = vnc.IsVNC('acme.com', 5900);
+// log(toJSON(isVNC));
+// ```
+func IsVNC(host string, port int) (IsVNCResponse, error) {
+ return memoizedisVNC(host, port)
+}
+
+// @memo
+func isVNC(host string, port int) (IsVNCResponse, error) {
resp := IsVNCResponse{}
timeout := 5 * time.Second
diff --git a/pkg/js/utils/nucleijs.go b/pkg/js/utils/nucleijs.go
new file mode 100644
index 0000000000..d3d4560813
--- /dev/null
+++ b/pkg/js/utils/nucleijs.go
@@ -0,0 +1,158 @@
+package utils
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+ "sync"
+
+ "github.com/dop251/goja"
+)
+
+// temporary on demand runtime to throw errors when vm is not available
+var (
+ tmpRuntime *goja.Runtime
+ runtimeInit func() = sync.OnceFunc(func() {
+ tmpRuntime = goja.New()
+ })
+)
+
+func getRuntime() *goja.Runtime {
+ runtimeInit()
+ return tmpRuntime
+}
+
+// NucleiJS is js bindings that handles goja runtime related issue
+// and allows setting a defer statements to close resources
+type NucleiJS struct {
+ vm *goja.Runtime
+ ObjectSig string
+}
+
+// NewNucleiJS creates a new nucleijs instance
+func NewNucleiJS(vm *goja.Runtime) *NucleiJS {
+ return &NucleiJS{vm: vm}
+}
+
+// internal runtime getter
+func (j *NucleiJS) runtime() *goja.Runtime {
+ if j == nil {
+ return getRuntime()
+ }
+ return j.vm
+}
+
+// see: https://arc.net/l/quote/wpenftpc for throwing docs
+
+// ThrowError throws an error in goja runtime if is not nil
+func (j *NucleiJS) ThrowError(err error) {
+ if err == nil {
+ return
+ }
+ panic(j.runtime().ToValue(err.Error()))
+}
+
+// HandleError handles error and throws a
+func (j *NucleiJS) HandleError(err error, msg ...string) {
+ if err == nil {
+ return
+ }
+ if len(msg) == 0 {
+ j.ThrowError(err)
+ }
+ j.Throw(fmt.Sprintf("%s: %s", strings.Join(msg, ":"), err.Error()))
+}
+
+// Throw throws an error in goja runtime
+func (j *NucleiJS) Throw(format string, args ...interface{}) {
+ panic(j.runtime().ToValue(fmt.Sprintf(format, args...)))
+}
+
+// GetArg returns argument at index from goja runtime if not found throws error
+func (j *NucleiJS) GetArg(args []goja.Value, index int) any {
+ if index >= len(args) {
+ j.Throw("Missing argument at index %v: %v", index, j.ObjectSig)
+ }
+ val := args[index]
+ if goja.IsUndefined(val) {
+ j.Throw("Missing argument at index %v: %v", index, j.ObjectSig)
+ }
+ return val.Export()
+}
+
+// GetArgSafe returns argument at index from goja runtime if not found returns default value
+func (j *NucleiJS) GetArgSafe(args []goja.Value, index int, defaultValue any) any {
+ if index >= len(args) {
+ return defaultValue
+ }
+ val := args[index]
+ if goja.IsUndefined(val) {
+ return defaultValue
+ }
+ return val.Export()
+}
+
+// Require throws an error if expression is false
+func (j *NucleiJS) Require(expr bool, msg string) {
+ if !expr {
+ j.Throw(msg)
+ }
+}
+
+// LinkConstructor links a type with invocation doing this allows
+// usage of instance of type in js
+func LinkConstructor[T any](call goja.ConstructorCall, vm *goja.Runtime, obj T) *goja.Object {
+ instance := vm.ToValue(obj).(*goja.Object)
+ _ = instance.SetPrototype(call.This.Prototype())
+ return instance
+}
+
+// GetStructType gets a type defined in go and passed as argument from goja runtime if not found throws error
+// Donot use this unless you are accepting a struct type from constructor
+func GetStructType[T any](nj *NucleiJS, args []goja.Value, index int, FuncSig string) T {
+ if nj == nil {
+ nj = &NucleiJS{}
+ }
+ if index >= len(args) {
+ if FuncSig == "" {
+ nj.Throw("Missing argument at index %v", index)
+ }
+ nj.Throw("Missing arguments expected : %v", FuncSig)
+ }
+ value := args[index]
+ // validate type
+ var ptr T
+ expected := reflect.ValueOf(ptr).Type()
+ argType := expected.Name()
+ valueType := value.ExportType().Name()
+
+ if argType != valueType {
+ nj.Throw("Type Mismatch expected %v got %v", argType, valueType)
+ }
+
+ ptrValue := reflect.New(expected).Elem()
+ ptrValue.Set(reflect.ValueOf(value.Export()))
+
+ return ptrValue.Interface().(T)
+}
+
+// GetStructTypeSafe gets an type defined in go and passed as argument from goja runtime if not found returns default value
+// Donot use this unless you are accepting a struct type from constructor
+func GetStructTypeSafe[T any](nj *NucleiJS, args []goja.Value, index int, defaultValue T) T {
+ if nj == nil {
+ nj = &NucleiJS{}
+ }
+ if index >= len(args) {
+ return defaultValue
+ }
+ value := args[index]
+ // validate type
+ var ptr T
+ argType := reflect.ValueOf(ptr).Type().Name()
+ valueType := value.ExportType().Name()
+
+ if argType != valueType {
+ return defaultValue
+ }
+ return value.ToObject(nj.runtime()).Export().(T)
+}
diff --git a/pkg/operators/matchers/matchers.go b/pkg/operators/matchers/matchers.go
index 29dd37b84a..95d1b2bf8f 100644
--- a/pkg/operators/matchers/matchers.go
+++ b/pkg/operators/matchers/matchers.go
@@ -101,7 +101,7 @@ type Matcher struct {
// []string{"/html/head/title[contains(text(), 'How to Find XPath')]"}
// - name: XPath Matcher for finding links with target="_blank"
// value: >
- // []string{"//a[@target="_blank"]"}
+ // []string{"//a[@target=\"_blank\"]"}
XPath []string `yaml:"xpath,omitempty" json:"xpath,omitempty" jsonschema:"title=xpath queries to match in response,description=xpath are the XPath queries that will be evaluated against the response part of nuclei matching rules"`
// description: |
// Encoding specifies the encoding for the words field if any.
diff --git a/pkg/operators/operators.go b/pkg/operators/operators.go
index a3b4fc561b..41fafc8368 100644
--- a/pkg/operators/operators.go
+++ b/pkg/operators/operators.go
@@ -287,7 +287,7 @@ func (operators *Operators) Execute(data map[string]interface{}, match MatchFunc
}
if isMatch, matched := match(data, matcher); isMatch {
if isDebug { // matchers without an explicit name or with AND condition should only be made visible if debug is enabled
- matcherName := getMatcherName(matcher, matcherIndex)
+ matcherName := GetMatcherName(matcher, matcherIndex)
result.Matches[matcherName] = matched
} else { // if it's a "named" matcher with OR condition, then display it
if matcherCondition == matchers.ORCondition && matcher.Name != "" {
@@ -299,7 +299,7 @@ func (operators *Operators) Execute(data map[string]interface{}, match MatchFunc
if len(result.DynamicValues) > 0 {
return result, true
}
- return nil, false
+ return result, false
}
}
@@ -313,6 +313,10 @@ func (operators *Operators) Execute(data map[string]interface{}, match MatchFunc
// Don't print if we have matchers, and they have not matched, regardless of extractor
if len(operators.Matchers) > 0 && !matches {
+ // if dynamic values are present then it is not a failure
+ if len(result.DynamicValues) > 0 {
+ return result, true
+ }
return nil, false
}
// Write a final string of output if matcher type is
@@ -320,10 +324,15 @@ func (operators *Operators) Execute(data map[string]interface{}, match MatchFunc
if len(result.Extracts) > 0 || len(result.OutputExtracts) > 0 || matches {
return result, true
}
+ // if dynamic values are present then it is not a failure
+ if len(result.DynamicValues) > 0 {
+ return result, true
+ }
return nil, false
}
-func getMatcherName(matcher *matchers.Matcher, matcherIndex int) string {
+// GetMatcherName returns matchername of given matcher
+func GetMatcherName(matcher *matchers.Matcher, matcherIndex int) string {
if matcher.Name != "" {
return matcher.Name
} else {
@@ -368,3 +377,20 @@ func getExtractedValue(values []string) any {
return values
}
}
+
+// EvalBoolSlice evaluates a slice of bools using a logical AND
+func EvalBoolSlice(slice []bool, isAnd bool) bool {
+ if len(slice) == 0 {
+ return false
+ }
+
+ result := slice[0]
+ for _, b := range slice[1:] {
+ if isAnd {
+ result = result && b
+ } else {
+ result = result || b
+ }
+ }
+ return result
+}
diff --git a/pkg/output/format_screen.go b/pkg/output/format_screen.go
index e902f942d1..d468981de0 100644
--- a/pkg/output/format_screen.go
+++ b/pkg/output/format_screen.go
@@ -60,7 +60,7 @@ func (w *StandardWriter) formatScreen(output *ResultEvent) []byte {
for i, item := range output.ExtractedResults {
// trim trailing space
item = strings.TrimSpace(item)
- item = strconv.QuoteToASCII(item)
+ item = strings.ReplaceAll(item, "\n", "\\n") // only replace newlines
builder.WriteString(w.aurora.BrightCyan(item).String())
if i != len(output.ExtractedResults)-1 {
diff --git a/pkg/output/output.go b/pkg/output/output.go
index d2893cc38e..6d9064b925 100644
--- a/pkg/output/output.go
+++ b/pkg/output/output.go
@@ -165,10 +165,20 @@ type ResultEvent struct {
// Lines is the line count for the specified match
Lines []int `json:"matched-line,omitempty"`
+ // IssueTrackers is the metadata for issue trackers
+ IssueTrackers map[string]IssueTrackerMetadata `json:"issue_trackers,omitempty"`
+
FileToIndexPosition map[string]int `json:"-"`
Error string `json:"error,omitempty"`
}
+type IssueTrackerMetadata struct {
+ // IssueID is the ID of the issue created
+ IssueID string `json:"id,omitempty"`
+ // IssueURL is the URL of the issue created
+ IssueURL string `json:"url,omitempty"`
+}
+
// NewStandardWriter creates a new output writer based on user configurations
func NewStandardWriter(options *types.Options) (*StandardWriter, error) {
resumeBool := false
diff --git a/pkg/parsers/parser.go b/pkg/parsers/parser.go
index 01bb3d3fdf..25e01b63f9 100644
--- a/pkg/parsers/parser.go
+++ b/pkg/parsers/parser.go
@@ -142,22 +142,28 @@ const (
SyntaxWarningStats = "syntax-warnings"
SyntaxErrorStats = "syntax-errors"
RuntimeWarningsStats = "runtime-warnings"
- UnsignedWarning = "unsigned-warnings"
+ UnsignedCodeWarning = "unsigned-warnings"
HeadlessFlagWarningStats = "headless-flag-missing-warnings"
TemplatesExecutedStats = "templates-executed"
CodeFlagWarningStats = "code-flag-missing-warnings"
+ FuzzFlagWarningStats = "fuzz-flag-missing-warnings"
+ // Note: this is redefined in workflows.go to avoid circular dependency, so make sure to keep it in sync
+ SkippedUnsignedStats = "skipped-unsigned-stats" // tracks loading of unsigned templates
)
func init() {
parsedTemplatesCache = cache.New()
+ config.DefaultConfig.RegisterGlobalCache(parsedTemplatesCache)
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(UnsignedCodeWarning, "Found %d unsigned or tampered code template (carefully examine before using it & use -sign flag to sign them)")
stats.NewEntry(HeadlessFlagWarningStats, "Excluded %d headless template[s] (disabled as default), use -headless option to run headless templates.")
stats.NewEntry(CodeFlagWarningStats, "Excluded %d code template[s] (disabled as default), use -code option to run code templates.")
stats.NewEntry(TemplatesExecutedStats, "Excluded %d template[s] with known weak matchers / tags excluded from default run using .nuclei-ignore")
+ stats.NewEntry(FuzzFlagWarningStats, "Excluded %d fuzz template[s] (disabled as default), use -fuzz option to run fuzz templates.")
+ stats.NewEntry(SkippedUnsignedStats, "Skipping %d unsigned template[s]")
}
// ParseTemplate parses a template and returns a *templates.Template structure
diff --git a/pkg/protocols/code/code.go b/pkg/protocols/code/code.go
index e2cbc42024..57eb9b8891 100644
--- a/pkg/protocols/code/code.go
+++ b/pkg/protocols/code/code.go
@@ -50,7 +50,7 @@ type Request struct {
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"`
+ Engine []string `yaml:"engine,omitempty" jsonschema:"title=engine,description=Engine"`
// description: |
// Engine Arguments
Args []string `yaml:"args,omitempty" jsonschema:"title=args,description=Args"`
@@ -83,7 +83,7 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
var src *gozero.Source
- src, err = gozero.NewSourceWithString(request.Source, request.Pattern)
+ src, err = gozero.NewSourceWithString(request.Source, request.Pattern, request.options.TemporaryDirectory)
if err != nil {
return err
}
@@ -125,7 +125,7 @@ func (request *Request) GetID() string {
// 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) (err error) {
- metaSrc, err := gozero.NewSourceWithString(input.MetaInput.Input, "")
+ metaSrc, err := gozero.NewSourceWithString(input.MetaInput.Input, "", request.options.TemporaryDirectory)
if err != nil {
return err
}
@@ -192,6 +192,10 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
dataOutputString := fmtStdout(gOutput.Stdout.String())
data := make(output.InternalEvent)
+ // also include all request variables in result event
+ for _, value := range metaSrc.Variables {
+ data[value.Name] = value.Value
+ }
data["type"] = request.Type().String()
data["response"] = dataOutputString // response contains filtered output (eg without trailing \n)
@@ -317,7 +321,7 @@ func interpretEnvVars(source string, vars map[string]interface{}) string {
// bash mode
if strings.Contains(source, "$") {
for k, v := range vars {
- source = strings.ReplaceAll(source, "$"+k, fmt.Sprintf("'%s'", v))
+ source = strings.ReplaceAll(source, "$"+k, fmt.Sprintf("%s", v))
}
}
// python mode
diff --git a/pkg/protocols/common/automaticscan/automaticscan.go b/pkg/protocols/common/automaticscan/automaticscan.go
index 4a6f049113..8193947190 100644
--- a/pkg/protocols/common/automaticscan/automaticscan.go
+++ b/pkg/protocols/common/automaticscan/automaticscan.go
@@ -15,7 +15,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
- "github.com/projectdiscovery/nuclei/v3/pkg/core/inputs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
@@ -45,7 +45,7 @@ type Options struct {
ExecuterOpts protocols.ExecutorOptions
Store *loader.Store
Engine *core.Engine
- Target core.InputProvider
+ Target provider.InputProvider
}
// Service is a service for automatic scan execution
@@ -53,7 +53,7 @@ type Service struct {
opts protocols.ExecutorOptions
store *loader.Store
engine *core.Engine
- target core.InputProvider
+ target provider.InputProvider
wappalyzer *wappalyzer.Wappalyze
childExecuter *core.ChildExecuter
httpclient *retryablehttp.Client
@@ -129,7 +129,7 @@ func (s *Service) Execute() error {
gologger.Info().Msgf("Executing Automatic scan on %d target[s]", s.target.Count())
// setup host concurrency
sg := sizedwaitgroup.New(s.opts.Options.BulkSize)
- s.target.Scan(func(value *contextargs.MetaInput) bool {
+ s.target.Iterate(func(value *contextargs.MetaInput) bool {
sg.Add()
go func(input *contextargs.MetaInput) {
defer sg.Done()
@@ -185,7 +185,8 @@ func (s *Service) executeAutomaticScanOnTarget(input *contextargs.MetaInput) {
execOptions := s.opts.Copy()
execOptions.Progress = &testutils.MockProgressClient{} // stats are not supported yet due to centralized logic and cannot be reinitialized
eng.SetExecuterOptions(execOptions)
- tmp := eng.ExecuteScanWithOpts(finalTemplates, &inputs.SimpleInputProvider{Inputs: []*contextargs.MetaInput{input}}, true)
+
+ tmp := eng.ExecuteScanWithOpts(finalTemplates, provider.NewSimpleInputProviderWithUrls(input.Input), true)
s.hasResults.Store(tmp.Load())
}
diff --git a/pkg/protocols/common/contextargs/metainput.go b/pkg/protocols/common/contextargs/metainput.go
index 4027bd7d01..afda2fda26 100644
--- a/pkg/protocols/common/contextargs/metainput.go
+++ b/pkg/protocols/common/contextargs/metainput.go
@@ -7,6 +7,8 @@ import (
"strings"
jsoniter "github.com/json-iterator/go"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
+ urlutil "github.com/projectdiscovery/utils/url"
)
// MetaInput represents a target with metadata (TODO: replace with https://github.com/projectdiscovery/metainput)
@@ -17,6 +19,9 @@ type MetaInput struct {
CustomIP string `json:"customIP,omitempty"`
// hash of the input
hash string `json:"-"`
+
+ // ReqResp is the raw request for the input
+ ReqResp *types.RequestResponse `json:"raw-request,omitempty"`
}
func (metaInput *MetaInput) marshalToBuffer() (bytes.Buffer, error) {
@@ -25,11 +30,31 @@ func (metaInput *MetaInput) marshalToBuffer() (bytes.Buffer, error) {
return b, err
}
+// Target returns the target of the metainput
+func (metaInput *MetaInput) Target() string {
+ if metaInput.ReqResp != nil && metaInput.ReqResp.URL.URL != nil {
+ return metaInput.ReqResp.URL.String()
+ }
+ return metaInput.Input
+}
+
+// URL returns request url
+func (metaInput *MetaInput) URL() (*urlutil.URL, error) {
+ instance, err := urlutil.ParseAbsoluteURL(metaInput.Target(), false)
+ if err != nil {
+ return nil, err
+ }
+ return instance, nil
+}
+
// ID returns a unique id/hash for metainput
func (metaInput *MetaInput) ID() string {
if metaInput.CustomIP != "" {
return fmt.Sprintf("%s-%s", metaInput.Input, metaInput.CustomIP)
}
+ if metaInput.ReqResp != nil {
+ return metaInput.ReqResp.ID()
+ }
return metaInput.Input
}
@@ -58,16 +83,23 @@ func (metaInput *MetaInput) Unmarshal(data string) error {
}
func (metaInput *MetaInput) Clone() *MetaInput {
- return &MetaInput{
+ input := &MetaInput{
Input: metaInput.Input,
CustomIP: metaInput.CustomIP,
}
+ if metaInput.ReqResp != nil {
+ input.ReqResp = metaInput.ReqResp.Clone()
+ }
+ return input
}
func (metaInput *MetaInput) PrettyPrint() string {
if metaInput.CustomIP != "" {
return fmt.Sprintf("%s [%s]", metaInput.Input, metaInput.CustomIP)
}
+ if metaInput.ReqResp != nil {
+ return fmt.Sprintf("%s [%s]", metaInput.ReqResp.URL.String(), metaInput.ReqResp.Request.Method)
+ }
return metaInput.Input
}
@@ -77,7 +109,11 @@ func (metaInput *MetaInput) GetScanHash(templateId string) string {
// 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)
+ var rawRequest string
+ if metaInput.ReqResp != nil {
+ rawRequest = metaInput.ReqResp.ID()
+ }
+ metaInput.hash = getMd5Hash(templateId + ":" + metaInput.Input + ":" + metaInput.CustomIP + rawRequest)
}
return metaInput.hash
}
diff --git a/pkg/protocols/common/fuzz/execute.go b/pkg/protocols/common/fuzz/execute.go
deleted file mode 100644
index 1a4700b0ba..0000000000
--- a/pkg/protocols/common/fuzz/execute.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package fuzz
-
-import (
- "regexp"
- "strings"
-
- "github.com/pkg/errors"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
- "github.com/projectdiscovery/retryablehttp-go"
- errorutil "github.com/projectdiscovery/utils/errors"
-)
-
-// ExecuteRuleInput is the input for rule Execute function
-type ExecuteRuleInput struct {
- // Input is the context args input
- Input *contextargs.Context
- // Callback is the callback for generated rule requests
- Callback func(GeneratedRequest) bool
- // InteractURLs contains interact urls for execute call
- InteractURLs []string
- // Values contains dynamic values for the rule
- Values map[string]interface{}
- // BaseRequest is the base http request for fuzzing rule
- BaseRequest *retryablehttp.Request
-}
-
-// GeneratedRequest is a single generated request for rule
-type GeneratedRequest struct {
- // Request is the http request for rule
- Request *retryablehttp.Request
- // InteractURLs is the list of interactsh urls
- InteractURLs []string
- // DynamicValues contains dynamic values map
- DynamicValues map[string]interface{}
-}
-
-// Execute executes a fuzzing rule accepting a callback on which
-// generated requests are returned.
-//
-// Input is not thread safe and should not be shared between concurrent
-// goroutines.
-func (rule *Rule) Execute(input *ExecuteRuleInput) error {
- if input.BaseRequest == nil {
- return errorutil.NewWithTag("fuzz", "base request is nil for rule %v", rule)
- }
- if !rule.isExecutable(input.BaseRequest) {
- return errorutil.NewWithTag("fuzz", "rule is not executable on %v", input.BaseRequest.URL.String())
- }
- baseValues := input.Values
- if rule.generator == nil {
- evaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(baseValues, rule.options.Interactsh)
- input.Values = generators.MergeMaps(evaluatedValues, baseValues, rule.options.Constants)
- input.InteractURLs = interactURLs
- err := rule.executeRuleValues(input)
- return err
- }
- iterator := rule.generator.NewIterator()
- for {
- values, next := iterator.Value()
- if !next {
- return nil
- }
- evaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(generators.MergeMaps(values, baseValues), rule.options.Interactsh)
- input.InteractURLs = interactURLs
- input.Values = generators.MergeMaps(values, evaluatedValues, baseValues, rule.options.Constants)
-
- if err := rule.executeRuleValues(input); err != nil {
- return err
- }
- }
-}
-
-// isExecutable returns true if the rule can be executed based on provided input
-func (rule *Rule) isExecutable(req *retryablehttp.Request) bool {
- if !req.Query().IsEmpty() && rule.partType == queryPartType {
- return true
- }
- if len(req.Header) > 0 && rule.partType == headersPartType {
- return true
- }
- return false
-}
-
-// executeRuleValues executes a rule with a set of values
-func (rule *Rule) executeRuleValues(input *ExecuteRuleInput) error {
- for _, payload := range rule.Fuzz {
- if err := rule.executePartRule(input, payload); err != nil {
- return err
- }
- }
- return nil
-}
-
-// Compile compiles a fuzzing rule and initializes it for operation
-func (rule *Rule) Compile(generator *generators.PayloadGenerator, options *protocols.ExecutorOptions) error {
- // If a payload generator is specified from base request, use it
- // for payload values.
- if generator != nil {
- rule.generator = generator
- }
- rule.options = options
-
- // Resolve the default enums
- if rule.Mode != "" {
- if valueType, ok := stringToModeType[rule.Mode]; !ok {
- return errors.Errorf("invalid mode value specified: %s", rule.Mode)
- } else {
- rule.modeType = valueType
- }
- } else {
- rule.modeType = multipleModeType
- }
- if rule.Part != "" {
- if valueType, ok := stringToPartType[rule.Part]; !ok {
- return errors.Errorf("invalid part value specified: %s", rule.Part)
- } else {
- rule.partType = valueType
- }
- } else {
- rule.partType = queryPartType
- }
-
- if rule.Type != "" {
- if valueType, ok := stringToRuleType[rule.Type]; !ok {
- return errors.Errorf("invalid type value specified: %s", rule.Type)
- } else {
- rule.ruleType = valueType
- }
- } else {
- rule.ruleType = replaceRuleType
- }
-
- // Initialize other required regexes and maps
- if len(rule.Keys) > 0 {
- rule.keysMap = make(map[string]struct{})
- }
- for _, key := range rule.Keys {
- rule.keysMap[strings.ToLower(key)] = struct{}{}
- }
- for _, value := range rule.ValuesRegex {
- compiled, err := regexp.Compile(value)
- if err != nil {
- return errors.Wrap(err, "could not compile value regex")
- }
- rule.valuesRegex = append(rule.valuesRegex, compiled)
- }
- for _, value := range rule.KeysRegex {
- compiled, err := regexp.Compile(value)
- if err != nil {
- return errors.Wrap(err, "could not compile key regex")
- }
- rule.keysRegex = append(rule.keysRegex, compiled)
- }
- return nil
-}
diff --git a/pkg/protocols/common/fuzz/execute_test.go b/pkg/protocols/common/fuzz/execute_test.go
deleted file mode 100644
index a922fbb7f7..0000000000
--- a/pkg/protocols/common/fuzz/execute_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package fuzz
-
-import (
- "github.com/projectdiscovery/retryablehttp-go"
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-func TestRuleIsExecutable(t *testing.T) {
- rule := &Rule{Part: "query"}
- err := rule.Compile(nil, nil)
- require.NoError(t, err, "could not compile rule")
-
- req, err := retryablehttp.NewRequest("GET", "https://example.com/?url=localhost", nil)
- require.NoError(t, err, "could not build request")
-
- result := rule.isExecutable(req)
- require.True(t, result, "could not get correct result")
-
- req, err = retryablehttp.NewRequest("GET", "https://example.com/", nil)
- require.NoError(t, err, "could not build request")
-
- result = rule.isExecutable(req)
- require.False(t, result, "could not get correct result")
-}
diff --git a/pkg/protocols/common/fuzz/parts.go b/pkg/protocols/common/fuzz/parts.go
deleted file mode 100644
index 533de60ae5..0000000000
--- a/pkg/protocols/common/fuzz/parts.go
+++ /dev/null
@@ -1,211 +0,0 @@
-package fuzz
-
-import (
- "context"
- "io"
- "net/http"
- "strings"
-
- "github.com/pkg/errors"
- "github.com/projectdiscovery/gologger"
- "github.com/projectdiscovery/useragent"
-
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
- "github.com/projectdiscovery/nuclei/v3/pkg/types"
- "github.com/projectdiscovery/retryablehttp-go"
- sliceutil "github.com/projectdiscovery/utils/slice"
- urlutil "github.com/projectdiscovery/utils/url"
-)
-
-// executePartRule executes part rules based on type
-func (rule *Rule) executePartRule(input *ExecuteRuleInput, payload string) error {
- switch rule.partType {
- case queryPartType:
- return rule.executeQueryPartRule(input, payload)
- case headersPartType:
- return rule.executeHeadersPartRule(input, payload)
- }
- return nil
-}
-
-// executeHeadersPartRule executes headers part rules
-func (rule *Rule) executeHeadersPartRule(input *ExecuteRuleInput, payload string) error {
- // clone the request to avoid modifying the original
- originalRequest := input.BaseRequest
- req := originalRequest.Clone(context.TODO())
- // Also clone headers
- headers := req.Header.Clone()
-
- for key, values := range originalRequest.Header {
- cloned := sliceutil.Clone(values)
- for i, value := range values {
- if !rule.matchKeyOrValue(key, value) {
- continue
- }
- var evaluated string
- evaluated, input.InteractURLs = rule.executeEvaluate(input, key, value, payload, input.InteractURLs)
- cloned[i] = evaluated
-
- if rule.modeType == singleModeType {
- headers[key] = cloned
- if err := rule.buildHeadersInput(input, headers, input.InteractURLs); err != nil && err != io.EOF {
- gologger.Error().Msgf("Could not build request for headers part rule %v: %s\n", rule, err)
- return err
- }
- cloned[i] = value // change back to previous value for headers
- }
- }
- headers[key] = cloned
- }
-
- if rule.modeType == multipleModeType {
- if err := rule.buildHeadersInput(input, headers, input.InteractURLs); err != nil {
- return err
- }
- }
- return nil
-}
-
-// executeQueryPartRule executes query part rules
-func (rule *Rule) executeQueryPartRule(input *ExecuteRuleInput, payload string) error {
- requestURL, err := urlutil.Parse(input.Input.MetaInput.Input)
- if err != nil {
- return err
- }
- origRequestURL := requestURL.Clone()
- // clone the params to avoid modifying the original
- temp := origRequestURL.Params.Clone()
-
- origRequestURL.Query().Iterate(func(key string, values []string) bool {
- cloned := sliceutil.Clone(values)
- for i, value := range values {
- if !rule.matchKeyOrValue(key, value) {
- continue
- }
- var evaluated string
- evaluated, input.InteractURLs = rule.executeEvaluate(input, key, value, payload, input.InteractURLs)
- cloned[i] = evaluated
-
- if rule.modeType == singleModeType {
- temp.Update(key, cloned)
- requestURL.Params = temp
- if qerr := rule.buildQueryInput(input, requestURL, input.InteractURLs); qerr != nil {
- err = qerr
- return false
- }
- cloned[i] = value // change back to previous value for temp
- }
- }
- temp.Update(key, cloned)
- return true
- })
-
- if rule.modeType == multipleModeType {
- requestURL.Params = temp
- if err := rule.buildQueryInput(input, requestURL, input.InteractURLs); err != nil {
- return err
- }
- }
-
- return err
-}
-
-// buildHeadersInput returns created request for a Headers Input
-func (rule *Rule) buildHeadersInput(input *ExecuteRuleInput, headers http.Header, interactURLs []string) error {
- var req *retryablehttp.Request
- if input.BaseRequest == nil {
- return errors.New("Base request cannot be nil when fuzzing headers")
- } else {
- req = input.BaseRequest.Clone(context.TODO())
- req.Header = headers
- // update host of request and not URL
- // URL.Host is used to dial the connection
- req.Request.Host = req.Header.Get("Host")
- }
- request := GeneratedRequest{
- Request: req,
- InteractURLs: interactURLs,
- DynamicValues: input.Values,
- }
- if !input.Callback(request) {
- return io.EOF
- }
- return nil
-}
-
-// buildQueryInput returns created request for a Query Input
-func (rule *Rule) buildQueryInput(input *ExecuteRuleInput, parsed *urlutil.URL, interactURLs []string) error {
- var req *retryablehttp.Request
- var err error
- if input.BaseRequest == nil {
- req, err = retryablehttp.NewRequestFromURL(http.MethodGet, parsed, nil)
- if err != nil {
- return err
- }
- userAgent := useragent.PickRandom()
- req.Header.Set("User-Agent", userAgent.Raw)
- } else {
- req = input.BaseRequest.Clone(context.TODO())
- req.SetURL(parsed)
- }
- request := GeneratedRequest{
- Request: req,
- InteractURLs: interactURLs,
- DynamicValues: input.Values,
- }
- if !input.Callback(request) {
- return types.ErrNoMoreRequests
- }
- return nil
-}
-
-// executeEvaluate executes evaluation of payload on a key and value and
-// returns completed values to be replaced and processed
-// for fuzzing.
-func (rule *Rule) executeEvaluate(input *ExecuteRuleInput, key, value, payload string, interactshURLs []string) (string, []string) {
- // TODO: Handle errors
- values := generators.MergeMaps(input.Values, map[string]interface{}{
- "value": value,
- })
- firstpass, _ := expressions.Evaluate(payload, values)
- interactData, interactshURLs := rule.options.Interactsh.Replace(firstpass, interactshURLs)
- evaluated, _ := expressions.Evaluate(interactData, values)
- replaced := rule.executeReplaceRule(input, value, evaluated)
- return replaced, interactshURLs
-}
-
-// executeReplaceRule executes replacement for a key and value
-func (rule *Rule) executeReplaceRule(input *ExecuteRuleInput, value, replacement string) string {
- var builder strings.Builder
- if rule.ruleType == prefixRuleType || rule.ruleType == postfixRuleType {
- builder.Grow(len(value) + len(replacement))
- }
- var returnValue string
-
- switch rule.ruleType {
- case prefixRuleType:
- builder.WriteString(replacement)
- builder.WriteString(value)
- returnValue = builder.String()
- case postfixRuleType:
- builder.WriteString(value)
- builder.WriteString(replacement)
- returnValue = builder.String()
- case infixRuleType:
- if len(value) <= 1 {
- builder.WriteString(value)
- builder.WriteString(replacement)
- returnValue = builder.String()
- } else {
- middleIndex := len(value) / 2
- builder.WriteString(value[:middleIndex])
- builder.WriteString(replacement)
- builder.WriteString(value[middleIndex:])
- returnValue = builder.String()
- }
- case replaceRuleType:
- returnValue = replacement
- }
- return returnValue
-}
diff --git a/pkg/protocols/common/fuzz/parts_test.go b/pkg/protocols/common/fuzz/parts_test.go
deleted file mode 100644
index 90b1b2ab65..0000000000
--- a/pkg/protocols/common/fuzz/parts_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package fuzz
-
-import (
- "github.com/projectdiscovery/retryablehttp-go"
- "net/http"
- "testing"
-
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
- "github.com/stretchr/testify/require"
-)
-
-func TestExecuteHeadersPartRule(t *testing.T) {
- options := &protocols.ExecutorOptions{
- Interactsh: &interactsh.Client{},
- }
- req, err := retryablehttp.NewRequest("GET", "http://localhost:8080/", nil)
- require.NoError(t, err, "can't build request")
-
- req.Header.Set("X-Custom-Foo", "foo")
- req.Header.Set("X-Custom-Bar", "bar")
-
- t.Run("single", func(t *testing.T) {
- rule := &Rule{
- ruleType: postfixRuleType,
- partType: headersPartType,
- modeType: singleModeType,
- options: options,
- }
- var generatedHeaders []http.Header
- err := rule.executeHeadersPartRule(&ExecuteRuleInput{
- Input: contextargs.New(),
- BaseRequest: req,
- Callback: func(gr GeneratedRequest) bool {
- generatedHeaders = append(generatedHeaders, gr.Request.Header.Clone())
- return true
- },
- }, "1337'")
- require.NoError(t, err, "could not execute part rule")
- require.ElementsMatch(t, []http.Header{
- {
- "X-Custom-Foo": {"foo1337'"},
- "X-Custom-Bar": {"bar"},
- },
- {
- "X-Custom-Foo": {"foo"},
- "X-Custom-Bar": {"bar1337'"},
- },
- }, generatedHeaders, "could not get generated headers")
- })
-
- t.Run("multiple", func(t *testing.T) {
- rule := &Rule{
- ruleType: postfixRuleType,
- partType: headersPartType,
- modeType: multipleModeType,
- options: options,
- }
- var generatedHeaders http.Header
- err := rule.executeHeadersPartRule(&ExecuteRuleInput{
- Input: contextargs.New(),
- BaseRequest: req,
- Callback: func(gr GeneratedRequest) bool {
- generatedHeaders = gr.Request.Header.Clone()
- return true
- },
- }, "1337'")
- require.NoError(t, err, "could not execute part rule")
- require.Equal(t, http.Header{
- "X-Custom-Foo": {"foo1337'"},
- "X-Custom-Bar": {"bar1337'"},
- }, generatedHeaders, "could not get generated headers")
- })
-}
-func TestExecuteQueryPartRule(t *testing.T) {
- URL := "http://localhost:8080/?url=localhost&mode=multiple&file=passwdfile"
- options := &protocols.ExecutorOptions{
- Interactsh: &interactsh.Client{},
- }
- t.Run("single", func(t *testing.T) {
- rule := &Rule{
- ruleType: postfixRuleType,
- partType: queryPartType,
- modeType: singleModeType,
- options: options,
- }
- var generatedURL []string
- input := contextargs.NewWithInput(URL)
- err := rule.executeQueryPartRule(&ExecuteRuleInput{
- Input: input,
- Callback: func(gr GeneratedRequest) bool {
- generatedURL = append(generatedURL, gr.Request.URL.String())
- return true
- },
- }, "1337'")
- require.NoError(t, err, "could not execute part rule")
- require.ElementsMatch(t, []string{
- "http://localhost:8080/?url=localhost1337'&mode=multiple&file=passwdfile",
- "http://localhost:8080/?url=localhost&mode=multiple1337'&file=passwdfile",
- "http://localhost:8080/?url=localhost&mode=multiple&file=passwdfile1337'",
- }, generatedURL, "could not get generated url")
- })
- t.Run("multiple", func(t *testing.T) {
- rule := &Rule{
- ruleType: postfixRuleType,
- partType: queryPartType,
- modeType: multipleModeType,
- options: options,
- }
- var generatedURL string
- input := contextargs.NewWithInput(URL)
- err := rule.executeQueryPartRule(&ExecuteRuleInput{
- Input: input,
- Callback: func(gr GeneratedRequest) bool {
- generatedURL = gr.Request.URL.String()
- return true
- },
- }, "1337'")
- require.NoError(t, err, "could not execute part rule")
- require.Equal(t, "http://localhost:8080/?url=localhost1337'&mode=multiple1337'&file=passwdfile1337'", generatedURL, "could not get generated url")
- })
-}
-
-func TestExecuteReplaceRule(t *testing.T) {
- tests := []struct {
- ruleType ruleType
- value string
- replacement string
- expected string
- }{
- {replaceRuleType, "test", "replacement", "replacement"},
- {prefixRuleType, "test", "prefix", "prefixtest"},
- {postfixRuleType, "test", "postfix", "testpostfix"},
- {infixRuleType, "", "infix", "infix"},
- {infixRuleType, "0", "infix", "0infix"},
- {infixRuleType, "test", "infix", "teinfixst"},
- }
- for _, test := range tests {
- rule := &Rule{ruleType: test.ruleType}
- returned := rule.executeReplaceRule(nil, test.value, test.replacement)
- require.Equal(t, test.expected, returned, "could not get correct value")
- }
-}
diff --git a/pkg/protocols/common/helpers/writer/writer.go b/pkg/protocols/common/helpers/writer/writer.go
index b68e584ff4..6666bde3c4 100644
--- a/pkg/protocols/common/helpers/writer/writer.go
+++ b/pkg/protocols/common/helpers/writer/writer.go
@@ -17,6 +17,11 @@ func WriteResult(data *output.InternalWrappedEvent, output output.Writer, progre
}
var matched bool
for _, result := range data.Results {
+ if issuesClient != nil {
+ if err := issuesClient.CreateIssue(result); err != nil {
+ gologger.Warning().Msgf("Could not create issue on tracker: %s", err)
+ }
+ }
if err := output.Write(result); err != nil {
gologger.Warning().Msgf("Could not write output event: %s\n", err)
}
@@ -24,12 +29,6 @@ func WriteResult(data *output.InternalWrappedEvent, output output.Writer, progre
matched = true
}
progress.IncrementMatched()
-
- if issuesClient != nil {
- if err := issuesClient.CreateIssue(result); err != nil {
- gologger.Warning().Msgf("Could not create issue on tracker: %s", err)
- }
- }
}
return matched
}
diff --git a/pkg/protocols/common/protocolstate/headless.go b/pkg/protocols/common/protocolstate/headless.go
index 0b58d5e326..755d367b97 100644
--- a/pkg/protocols/common/protocolstate/headless.go
+++ b/pkg/protocols/common/protocolstate/headless.go
@@ -1,6 +1,7 @@
package protocolstate
import (
+ "net"
"strings"
"github.com/go-rod/rod"
@@ -81,6 +82,24 @@ func IsHostAllowed(targetUrl string) bool {
if NetworkPolicy == nil {
return true
}
+ sepCount := strings.Count(targetUrl, ":")
+ if sepCount > 1 {
+ // most likely a ipv6 address (parse url and validate host)
+ return NetworkPolicy.Validate(targetUrl)
+ }
+ if sepCount == 1 {
+ host, _, _ := net.SplitHostPort(targetUrl)
+ if _, ok := NetworkPolicy.ValidateHost(host); !ok {
+ return false
+ }
+ return true
+ // portInt, _ := strconv.Atoi(port)
+ // fixme: broken port validation logic in networkpolicy
+ // if !NetworkPolicy.ValidatePort(portInt) {
+ // return false
+ // }
+ }
+ // just a hostname or ip without port
_, ok := NetworkPolicy.ValidateHost(targetUrl)
return ok
}
diff --git a/pkg/protocols/common/protocolstate/memoizer.go b/pkg/protocols/common/protocolstate/memoizer.go
new file mode 100644
index 0000000000..812472d13e
--- /dev/null
+++ b/pkg/protocols/common/protocolstate/memoizer.go
@@ -0,0 +1,15 @@
+package protocolstate
+
+import (
+ "github.com/projectdiscovery/utils/memoize"
+)
+
+var Memoizer *memoize.Memoizer
+
+func init() {
+ var err error
+ Memoizer, err = memoize.New(memoize.WithMaxSize(1500))
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/pkg/protocols/common/protocolstate/state.go b/pkg/protocols/common/protocolstate/state.go
index cc5845862b..f840934fb0 100644
--- a/pkg/protocols/common/protocolstate/state.go
+++ b/pkg/protocols/common/protocolstate/state.go
@@ -118,6 +118,7 @@ func Init(options *types.Options) error {
}
if options.SystemResolvers {
+ opts.ResolversFile = true
opts.EnableFallback = true
}
if options.ResolversFile != "" {
diff --git a/pkg/protocols/headless/headless.go b/pkg/protocols/headless/headless.go
index 81cb3ca43c..373880f1a7 100644
--- a/pkg/protocols/headless/headless.go
+++ b/pkg/protocols/headless/headless.go
@@ -3,10 +3,10 @@ package headless
import (
"github.com/pkg/errors"
+ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz"
useragent "github.com/projectdiscovery/nuclei/v3/pkg/model/types/userAgent"
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/fuzz"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
uagent "github.com/projectdiscovery/useragent"
diff --git a/pkg/protocols/headless/request.go b/pkg/protocols/headless/request.go
index f800c261cf..f45afdd35e 100644
--- a/pkg/protocols/headless/request.go
+++ b/pkg/protocols/headless/request.go
@@ -12,10 +12,10 @@ import (
"golang.org/x/exp/maps"
"github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/fuzz"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
diff --git a/pkg/protocols/http/build_request.go b/pkg/protocols/http/build_request.go
index b526d9d291..7408925a2b 100644
--- a/pkg/protocols/http/build_request.go
+++ b/pkg/protocols/http/build_request.go
@@ -13,6 +13,7 @@ import (
"github.com/projectdiscovery/useragent"
"github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
@@ -49,6 +50,32 @@ type generatedRequest struct {
customCancelFunction context.CancelFunc
}
+// ApplyAuth applies the auth provider to the generated request
+func (g *generatedRequest) ApplyAuth(provider authprovider.AuthProvider) {
+ if provider == nil {
+ return
+ }
+ if g.request != nil {
+ auth := provider.LookupURLX(g.request.URL)
+ if auth != nil {
+ auth.ApplyOnRR(g.request)
+ }
+ }
+ if g.rawRequest != nil {
+ parsed, err := urlutil.ParseAbsoluteURL(g.rawRequest.FullURL, true)
+ if err != nil {
+ gologger.Warning().Msgf("[authprovider] Could not parse URL %s: %s\n", g.rawRequest.FullURL, err)
+ return
+ }
+ auth := provider.LookupURLX(parsed)
+ if auth != nil {
+ // here we need to apply it custom because we don't have a standard/official
+ // rawhttp request format ( which we probably should have )
+ g.rawRequest.ApplyAuthStrategy(auth)
+ }
+ }
+}
+
func (g *generatedRequest) URL() string {
if g.request != nil {
return g.request.URL.String()
@@ -97,7 +124,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context,
}
// Parse target url
- parsed, err := urlutil.Parse(input.MetaInput.Input)
+ parsed, err := urlutil.ParseAbsoluteURL(input.MetaInput.Input, false)
if err != nil {
return nil, err
}
@@ -153,7 +180,7 @@ func (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context,
return r.generateRawRequest(ctx, reqData, parsed, finalVars, payloads)
}
- reqURL, err := urlutil.ParseURL(reqData, true)
+ reqURL, err := urlutil.ParseAbsoluteURL(reqData, true)
if err != nil {
return nil, errorutil.NewWithTag("http", "failed to parse url %v while creating http request", reqData)
}
@@ -291,8 +318,7 @@ func (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest st
unsafeReq := &generatedRequest{rawRequest: rawRequestData, meta: generatorValues, original: r.request, interactshURLs: r.interactshURLs}
return unsafeReq, nil
}
-
- urlx, err := urlutil.ParseURL(rawRequestData.FullURL, true)
+ urlx, err := urlutil.ParseAbsoluteURL(rawRequestData.FullURL, true)
if err != nil {
return nil, errorutil.NewWithErr(err).Msgf("failed to create request with url %v got %v", rawRequestData.FullURL, err).WithTag("raw")
}
diff --git a/pkg/protocols/http/http.go b/pkg/protocols/http/http.go
index cd88d3048d..ede8291e4f 100644
--- a/pkg/protocols/http/http.go
+++ b/pkg/protocols/http/http.go
@@ -8,10 +8,11 @@ import (
json "github.com/json-iterator/go"
"github.com/pkg/errors"
+ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz"
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
+ "github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/fuzz"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool"
httputil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http"
@@ -135,7 +136,7 @@ type Request struct {
// description: |
// SelfContained specifies if the request is self-contained.
- SelfContained bool `yaml:"-" json:"-"`
+ SelfContained bool `yaml:"self-contained,omitempty" json:"self-contained,omitempty"`
// description: |
// Signature is the request signature method
@@ -208,6 +209,14 @@ type Request struct {
// description: |
// DisablePathAutomerge disables merging target url path with raw request path
DisablePathAutomerge bool `yaml:"disable-path-automerge,omitempty" json:"disable-path-automerge,omitempty" jsonschema:"title=disable auto merging of path,description=Disable merging target url path with raw request path"`
+ // description: |
+ // Filter is matcher-like field to check if fuzzing should be performed on this request or not
+ FuzzingFilter []*matchers.Matcher `yaml:"filters,omitempty" json:"filter,omitempty" jsonschema:"title=filter for fuzzing,description=Filter is matcher-like field to check if fuzzing should be performed on this request or not"`
+ // description: |
+ // Filter condition is the condition to apply on the filter (AND/OR). Default is OR
+ FuzzingFilterCondition string `yaml:"filters-condition,omitempty" json:"filter-condition,omitempty" jsonschema:"title=condition between the filters,description=Conditions between the filters,enum=and,enum=or"`
+ // cached variables that may be used along with request.
+ fuzzingFilterCondition matchers.ConditionType `yaml:"-" json:"-"`
}
// Options returns executer options for http request
@@ -314,6 +323,20 @@ func (request *Request) Compile(options *protocols.ExecutorOptions) error {
request.CompiledOperators = compiled
}
+ // === fuzzing filters ===== //
+
+ if request.FuzzingFilterCondition != "" {
+ request.fuzzingFilterCondition = matchers.ConditionTypes[request.FuzzingFilterCondition]
+ } else {
+ request.fuzzingFilterCondition = matchers.ORCondition
+ }
+
+ for _, filter := range request.FuzzingFilter {
+ if err := filter.CompileMatchers(); err != nil {
+ return errors.Wrap(err, "could not compile matcher")
+ }
+ }
+
// Resolve payload paths from vars if they exists
for name, payload := range request.options.Options.Vars.AsMap() {
payloadStr, ok := payload.(string)
diff --git a/pkg/protocols/http/httputils/chain.go b/pkg/protocols/http/httputils/chain.go
deleted file mode 100644
index 6ff9097922..0000000000
--- a/pkg/protocols/http/httputils/chain.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package httputils
-
-import (
- "bytes"
- "fmt"
- "net/http"
- "sync"
-
- protoUtil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
-)
-
-// use buffer pool for storing response body
-// and reuse it for each request
-var bufPool = sync.Pool{
- New: func() any {
- // The Pool's New function should generally only return pointer
- // types, since a pointer can be put into the return interface
- // value without an allocation:
- return new(bytes.Buffer)
- },
-}
-
-// getBuffer returns a buffer from the pool
-func getBuffer() *bytes.Buffer {
- return bufPool.Get().(*bytes.Buffer)
-}
-
-// putBuffer returns a buffer to the pool
-func putBuffer(buf *bytes.Buffer) {
- buf.Reset()
- bufPool.Put(buf)
-}
-
-// Performance Notes:
-// do not use http.Response once we create ResponseChain from it
-// as this reuses buffers and saves allocations and also drains response
-// body automatically.
-// In required cases it can be used but should never be used for anything
-// related to response body.
-// Bytes.Buffer returned by getters should not be used and are only meant for convinience
-// purposes like .String() or .Bytes() calls.
-// Remember to call Close() on ResponseChain once you are done with it.
-
-// ResponseChain is a response chain for a http request
-// on every call to previous it returns the previous response
-// if it was redirected.
-type ResponseChain struct {
- headers *bytes.Buffer
- body *bytes.Buffer
- fullResponse *bytes.Buffer
- resp *http.Response
- reloaded bool // if response was reloaded to its previous redirect
-}
-
-// NewResponseChain creates a new response chain for a http request
-// with a maximum body size. (if -1 stick to default 4MB)
-func NewResponseChain(resp *http.Response, maxBody int64) *ResponseChain {
- if _, ok := resp.Body.(protoUtil.LimitResponseBody); !ok {
- resp.Body = protoUtil.NewLimitResponseBodyWithSize(resp.Body, maxBody)
- }
- return &ResponseChain{
- headers: getBuffer(),
- body: getBuffer(),
- fullResponse: getBuffer(),
- resp: resp,
- }
-}
-
-// Response returns the current response in the chain
-func (r *ResponseChain) Headers() *bytes.Buffer {
- return r.headers
-}
-
-// Body returns the current response body in the chain
-func (r *ResponseChain) Body() *bytes.Buffer {
- return r.body
-}
-
-// FullResponse returns the current response in the chain
-func (r *ResponseChain) FullResponse() *bytes.Buffer {
- return r.fullResponse
-}
-
-// previous updates response pointer to previous response
-// if it was redirected and returns true else false
-func (r *ResponseChain) Previous() bool {
- if r.resp != nil && r.resp.Request != nil && r.resp.Request.Response != nil {
- r.resp = r.resp.Request.Response
- r.reloaded = true
- return true
- }
- return false
-}
-
-// Fill buffers
-func (r *ResponseChain) Fill() error {
- r.reset()
- if r.resp == nil {
- return fmt.Errorf("response is nil")
- }
-
- // load headers
- err := DumpResponseIntoBuffer(r.resp, false, r.headers)
- if err != nil {
- return fmt.Errorf("error dumping response headers: %s", err)
- }
-
- if r.resp.StatusCode != http.StatusSwitchingProtocols && !r.reloaded {
- // Note about reloaded:
- // this is a known behaviour existing from earlier version
- // when redirect is followed and operators are executed on all redirect chain
- // body of those requests is not available since its already been redirected
- // This is not a issue since redirect happens with empty body according to RFC
- // but this may be required sometimes
- // Solution: Manual redirect using dynamic matchers or hijack redirected responses
- // at transport level at replace with bytes buffer and then use it
-
- // load body
- err = readNNormalizeRespBody(r, r.body)
- if err != nil {
- return fmt.Errorf("error reading response body: %s", err)
- }
-
- // response body should not be used anymore
- // drain and close
- DrainResponseBody(r.resp)
- }
-
- // join headers and body
- r.fullResponse.Write(r.headers.Bytes())
- r.fullResponse.Write(r.body.Bytes())
- return nil
-}
-
-// Close the response chain and releases the buffers.
-func (r *ResponseChain) Close() {
- putBuffer(r.headers)
- putBuffer(r.body)
- putBuffer(r.fullResponse)
- r.headers = nil
- r.body = nil
- r.fullResponse = nil
-}
-
-// Has returns true if the response chain has a response
-func (r *ResponseChain) Has() bool {
- return r.resp != nil
-}
-
-// Request is request of current response
-func (r *ResponseChain) Request() *http.Request {
- if r.resp == nil {
- return nil
- }
- return r.resp.Request
-}
-
-// Response is response of current response
-func (r *ResponseChain) Response() *http.Response {
- return r.resp
-}
-
-// reset without releasing the buffers
-// useful for redirect chain
-func (r *ResponseChain) reset() {
- r.headers.Reset()
- r.body.Reset()
- r.fullResponse.Reset()
-}
diff --git a/pkg/protocols/http/httputils/internal.go b/pkg/protocols/http/httputils/internal.go
deleted file mode 100644
index 98f261328e..0000000000
--- a/pkg/protocols/http/httputils/internal.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package httputils
-
-import (
- "bytes"
- "errors"
- "io"
- "net/http"
- "strings"
-)
-
-// implementations copied from stdlib
-
-// errNoBody is a sentinel error value used by failureToReadBody so we
-// can detect that the lack of body was intentional.
-var errNoBody = errors.New("sentinel error value")
-
-// failureToReadBody is an io.ReadCloser that just returns errNoBody on
-// Read. It's swapped in when we don't actually want to consume
-// the body, but need a non-nil one, and want to distinguish the
-// error from reading the dummy body.
-type failureToReadBody struct{}
-
-func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody }
-func (failureToReadBody) Close() error { return nil }
-
-// emptyBody is an instance of empty reader.
-var emptyBody = io.NopCloser(strings.NewReader(""))
-
-// drainBody reads all of b to memory and then returns two equivalent
-// ReadClosers yielding the same bytes.
-//
-// It returns an error if the initial slurp of all bytes fails. It does not attempt
-// to make the returned ReadClosers have identical error-matching behavior.
-func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
- if b == nil || b == http.NoBody {
- // No copying needed. Preserve the magic sentinel meaning of NoBody.
- return http.NoBody, http.NoBody, nil
- }
- var buf bytes.Buffer
- if _, err = buf.ReadFrom(b); err != nil {
- return nil, b, err
- }
- if err = b.Close(); err != nil {
- return nil, b, err
- }
- return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil
-}
diff --git a/pkg/protocols/http/httputils/normalization.go b/pkg/protocols/http/httputils/normalization.go
deleted file mode 100644
index 010e7aa212..0000000000
--- a/pkg/protocols/http/httputils/normalization.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package httputils
-
-import (
- "bytes"
- "compress/gzip"
- "compress/zlib"
- "io"
- "net/http"
- "strings"
-
- "github.com/pkg/errors"
- "golang.org/x/text/encoding/simplifiedchinese"
- "golang.org/x/text/transform"
-
- stringsutil "github.com/projectdiscovery/utils/strings"
-)
-
-// readNNormalizeRespBody performs normalization on the http response object.
-// and fills body buffer with actual response body.
-func readNNormalizeRespBody(rc *ResponseChain, body *bytes.Buffer) (err error) {
- response := rc.resp
- // net/http doesn't automatically decompress the response body if an
- // encoding has been specified by the user in the request so in case we have to
- // manually do it.
-
- origBody := rc.resp.Body
- // wrap with decode if applicable
- wrapped, err := wrapDecodeReader(response)
- if err != nil {
- wrapped = origBody
- }
- // read response body to buffer
- _, err = body.ReadFrom(wrapped)
- if err != nil {
- if strings.Contains(err.Error(), "gzip: invalid header") {
- // its invalid gzip but we will still use it from original body
- _, err = body.ReadFrom(origBody)
- if err != nil {
- return errors.Wrap(err, "could not read response body after gzip error")
- }
- }
- if stringsutil.ContainsAny(err.Error(), "unexpected EOF", "read: connection reset by peer", "user canceled") {
- // keep partial body and continue (skip error) (add meta header in response for debugging)
- response.Header.Set("x-nuclei-ignore-error", err.Error())
- return nil
- }
- return errors.Wrap(err, "could not read response body")
- }
- return nil
-}
-
-// wrapDecodeReader wraps a decompression reader around the response body if it's compressed
-// using gzip or deflate.
-func wrapDecodeReader(resp *http.Response) (rc io.ReadCloser, err error) {
- switch resp.Header.Get("Content-Encoding") {
- case "gzip":
- rc, err = gzip.NewReader(resp.Body)
- case "deflate":
- rc, err = zlib.NewReader(resp.Body)
- default:
- rc = resp.Body
- }
- if err != nil {
- return nil, err
- }
- // handle GBK encoding
- if isContentTypeGbk(resp.Header.Get("Content-Type")) {
- rc = io.NopCloser(transform.NewReader(rc, simplifiedchinese.GBK.NewDecoder()))
- }
- return rc, nil
-}
-
-// isContentTypeGbk checks if the content-type header is gbk
-func isContentTypeGbk(contentType string) bool {
- contentType = strings.ToLower(contentType)
- return stringsutil.ContainsAny(contentType, "gbk", "gb2312", "gb18030")
-}
diff --git a/pkg/protocols/http/httputils/response.go b/pkg/protocols/http/httputils/response.go
deleted file mode 100644
index 5803e1271d..0000000000
--- a/pkg/protocols/http/httputils/response.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package httputils
-
-import (
- "bytes"
- "fmt"
- "io"
- "net/http"
-
- protocolutil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
-)
-
-// DumpResponseIntoBuffer dumps a http response without allocating a new buffer
-// for the response body.
-func DumpResponseIntoBuffer(resp *http.Response, body bool, buff *bytes.Buffer) (err error) {
- if resp == nil {
- return fmt.Errorf("response is nil")
- }
- save := resp.Body
- savecl := resp.ContentLength
-
- if !body {
- // For content length of zero. Make sure the body is an empty
- // reader, instead of returning error through failureToReadBody{}.
- if resp.ContentLength == 0 {
- resp.Body = emptyBody
- } else {
- resp.Body = failureToReadBody{}
- }
- } else if resp.Body == nil {
- resp.Body = emptyBody
- } else {
- save, resp.Body, err = drainBody(resp.Body)
- if err != nil {
- return err
- }
- }
- err = resp.Write(buff)
- if err == errNoBody {
- err = nil
- }
- resp.Body = save
- resp.ContentLength = savecl
- return
-}
-
-// DrainResponseBody drains the response body and closes it.
-func DrainResponseBody(resp *http.Response) {
- defer resp.Body.Close()
- // don't reuse connection and just close if body length is more than 2 * MaxBodyRead
- // to avoid DOS
- _, _ = io.CopyN(io.Discard, resp.Body, 2*protocolutil.MaxBodyRead)
-}
diff --git a/pkg/protocols/http/httputils/spm.go b/pkg/protocols/http/httputils/spm.go
new file mode 100644
index 0000000000..ccaa9a85c9
--- /dev/null
+++ b/pkg/protocols/http/httputils/spm.go
@@ -0,0 +1,159 @@
+package httputils
+
+import (
+ "context"
+ "sync"
+
+ "github.com/remeh/sizedwaitgroup"
+)
+
+// WorkPoolType is the type of work pool to use
+type WorkPoolType uint
+
+const (
+ // Blocking blocks addition of new work when the pool is full
+ Blocking WorkPoolType = iota
+ // NonBlocking does not block addition of new work when the pool is full
+ NonBlocking
+)
+
+// StopAtFirstMatchHandler is a handler that executes
+// request and stops on first match
+type StopAtFirstMatchHandler[T any] struct {
+ once sync.Once
+ // Result Channel
+ ResultChan chan T
+
+ // work pool and its type
+ poolType WorkPoolType
+ sgPool sizedwaitgroup.SizedWaitGroup
+ wgPool *sync.WaitGroup
+
+ // internal / unexported
+ ctx context.Context
+ cancel context.CancelFunc
+ internalWg *sync.WaitGroup
+ results []T
+ stopEnabled bool
+}
+
+// NewBlockingSPMHandler creates a new stop at first match handler
+func NewBlockingSPMHandler[T any](ctx context.Context, size int, spm bool) *StopAtFirstMatchHandler[T] {
+ ctx1, cancel := context.WithCancel(ctx)
+ s := &StopAtFirstMatchHandler[T]{
+ ResultChan: make(chan T, 1),
+ poolType: Blocking,
+ sgPool: sizedwaitgroup.New(size),
+ internalWg: &sync.WaitGroup{},
+ ctx: ctx1,
+ cancel: cancel,
+ stopEnabled: spm,
+ }
+ s.internalWg.Add(1)
+ go s.run(ctx)
+ return s
+}
+
+// NewNonBlockingSPMHandler creates a new stop at first match handler
+func NewNonBlockingSPMHandler[T any](ctx context.Context, spm bool) *StopAtFirstMatchHandler[T] {
+ ctx1, cancel := context.WithCancel(ctx)
+ s := &StopAtFirstMatchHandler[T]{
+ ResultChan: make(chan T, 1),
+ poolType: NonBlocking,
+ wgPool: &sync.WaitGroup{},
+ internalWg: &sync.WaitGroup{},
+ ctx: ctx1,
+ cancel: cancel,
+ stopEnabled: spm,
+ }
+ s.internalWg.Add(1)
+ go s.run(ctx)
+ return s
+}
+
+// Trigger triggers the stop at first match handler and stops the execution of
+// existing requests
+func (h *StopAtFirstMatchHandler[T]) Trigger() {
+ if h.stopEnabled {
+ h.cancel()
+ }
+}
+
+// MatchCallback is called when a match is found
+// input fn should be the callback that is intended to be called
+// if stop at first is enabled and other conditions are met
+// if it does not meet above conditions, use of this function is discouraged
+func (h *StopAtFirstMatchHandler[T]) MatchCallback(fn func()) {
+ if !h.stopEnabled {
+ fn()
+ return
+ }
+ h.once.Do(fn)
+}
+
+// run runs the internal handler
+func (h *StopAtFirstMatchHandler[T]) run(ctx context.Context) {
+ defer h.internalWg.Done()
+ for {
+ select {
+ case <-ctx.Done():
+ case val, ok := <-h.ResultChan:
+ if !ok {
+ return
+ }
+ h.results = append(h.results, val)
+ }
+ }
+}
+
+// Done returns a channel with the context done signal when stop at first match is detected
+func (h *StopAtFirstMatchHandler[T]) Done() <-chan struct{} {
+ return h.ctx.Done()
+}
+
+// FoundFirstMatch returns true if first match was found
+// in stop at first match mode
+func (h *StopAtFirstMatchHandler[T]) FoundFirstMatch() bool {
+ if h.ctx.Err() != nil && h.stopEnabled {
+ return true
+ }
+ return false
+}
+
+// Acquire acquires a new work
+func (h *StopAtFirstMatchHandler[T]) Acquire() {
+ switch h.poolType {
+ case Blocking:
+ h.sgPool.Add()
+ case NonBlocking:
+ h.wgPool.Add(1)
+ }
+}
+
+// Release releases a work
+func (h *StopAtFirstMatchHandler[T]) Release() {
+ switch h.poolType {
+ case Blocking:
+ h.sgPool.Done()
+ case NonBlocking:
+ h.wgPool.Done()
+ }
+}
+
+// Wait waits for all work to be done
+func (h *StopAtFirstMatchHandler[T]) Wait() {
+ switch h.poolType {
+ case Blocking:
+ h.sgPool.Wait()
+ case NonBlocking:
+ h.wgPool.Wait()
+ }
+ // after waiting it closes the error channel
+ close(h.ResultChan)
+ h.internalWg.Wait()
+}
+
+// CombinedResults returns the combined results
+func (h *StopAtFirstMatchHandler[T]) CombinedResults() []T {
+ return h.results
+}
diff --git a/pkg/protocols/http/raw/raw.go b/pkg/protocols/http/raw/raw.go
index d40046830c..a1eb544a20 100644
--- a/pkg/protocols/http/raw/raw.go
+++ b/pkg/protocols/http/raw/raw.go
@@ -3,11 +3,14 @@ package raw
import (
"bufio"
"bytes"
+ "encoding/base64"
"errors"
"fmt"
"io"
"strings"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx"
"github.com/projectdiscovery/rawhttp/client"
errorutil "github.com/projectdiscovery/utils/errors"
stringsutil "github.com/projectdiscovery/utils/strings"
@@ -267,3 +270,44 @@ func (r *Request) TryFillCustomHeaders(headers []string) error {
return errors.New("no host header found")
}
+
+// ApplyAuthStrategy applies the auth strategy to the request
+func (r *Request) ApplyAuthStrategy(strategy authx.AuthStrategy) {
+ if strategy == nil {
+ return
+ }
+ switch s := strategy.(type) {
+ case *authx.QueryAuthStrategy:
+ parsed, err := urlutil.Parse(r.FullURL)
+ if err != nil {
+ gologger.Error().Msgf("auth strategy failed to parse url: %s got %v", r.FullURL, err)
+ return
+ }
+ _ = parsed
+ for _, p := range s.Data.Params {
+ parsed.Params.Add(p.Key, p.Value)
+ }
+ case *authx.CookiesAuthStrategy:
+ var buff bytes.Buffer
+ for _, cookie := range s.Data.Cookies {
+ buff.WriteString(fmt.Sprintf("%s=%s; ", cookie.Key, cookie.Value))
+ }
+ if buff.Len() > 0 {
+ if val, ok := r.Headers["Cookie"]; ok {
+ r.Headers["Cookie"] = strings.TrimSuffix(strings.TrimSpace(val), ";") + "; " + buff.String()
+ } else {
+ r.Headers["Cookie"] = buff.String()
+ }
+ }
+ case *authx.HeadersAuthStrategy:
+ for _, header := range s.Data.Headers {
+ r.Headers[header.Key] = header.Value
+ }
+ case *authx.BearerTokenAuthStrategy:
+ r.Headers["Authorization"] = "Bearer " + s.Data.Token
+ case *authx.BasicAuthStrategy:
+ r.Headers["Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(s.Data.Username+":"+s.Data.Password))
+ default:
+ gologger.Warning().Msgf("[raw-request] unknown auth strategy: %T", s)
+ }
+}
diff --git a/pkg/protocols/http/request.go b/pkg/protocols/http/request.go
index 72d3d9bda7..81df15b20c 100644
--- a/pkg/protocols/http/request.go
+++ b/pkg/protocols/http/request.go
@@ -10,10 +10,10 @@ import (
"strconv"
"strings"
"sync"
+ "sync/atomic"
"time"
"github.com/pkg/errors"
- "github.com/remeh/sizedwaitgroup"
"go.uber.org/multierr"
"moul.io/http2curl"
@@ -24,7 +24,6 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
- "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/fuzz"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter"
@@ -34,11 +33,12 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httputils"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signer"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signerpool"
- protocolutil "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/rawhttp"
convUtil "github.com/projectdiscovery/utils/conversion"
+ errorutil "github.com/projectdiscovery/utils/errors"
+ httpUtils "github.com/projectdiscovery/utils/http"
"github.com/projectdiscovery/utils/reader"
sliceutil "github.com/projectdiscovery/utils/slice"
stringsutil "github.com/projectdiscovery/utils/strings"
@@ -49,6 +49,10 @@ const (
defaultMaxWorkers = 150
)
+var (
+ MaxBodyRead = int64(1 << 22) // 4MB using shift operator
+)
+
// Type returns the type of the protocol request
func (request *Request) Type() templateTypes.ProtocolType {
return templateTypes.HTTPProtocol
@@ -104,36 +108,90 @@ func (request *Request) executeRaceRequest(input *contextargs.Context, previous
generatedRequests = append(generatedRequests, generatedRequest)
}
- wg := sync.WaitGroup{}
- var requestErr error
- mutex := &sync.Mutex{}
+ shouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)
+ spmHandler := httputils.NewNonBlockingSPMHandler[error](ctx, shouldStop)
+ gotMatches := &atomic.Bool{}
+ // wrappedCallback is a callback that wraps the original callback
+ // to implement stop at first match logic
+ wrappedCallback := func(event *output.InternalWrappedEvent) {
+ if !event.HasOperatorResult() {
+ callback(event) // not required but we can allow it
+ return
+ }
+ // this will execute match condition such that if stop at first match is enabled
+ // this will be only executed once
+ spmHandler.MatchCallback(func() {
+ gotMatches.Store(true)
+ callback(event)
+ })
+ if shouldStop {
+ // stop all running requests and exit
+ spmHandler.Trigger()
+ }
+ }
+
for i := 0; i < request.RaceNumberRequests; i++ {
- wg.Add(1)
+ spmHandler.Acquire()
+ // execute http request
go func(httpRequest *generatedRequest) {
- defer wg.Done()
- err := request.executeRequest(input, httpRequest, previous, false, callback, 0)
- mutex.Lock()
- if err != nil {
- requestErr = multierr.Append(requestErr, err)
+ defer spmHandler.Release()
+ defer func() {
+ if r := recover(); r != nil {
+ gologger.Verbose().Msgf("[%s] Recovered from panic: %v\n", request.options.TemplateID, r)
+ }
+ }()
+ if spmHandler.FoundFirstMatch() {
+ // stop sending more requests condition is met
+ return
+ }
+
+ select {
+ case <-spmHandler.Done():
+ return
+ case spmHandler.ResultChan <- request.executeRequest(input, httpRequest, previous, false, wrappedCallback, 0):
+ return
}
- mutex.Unlock()
}(generatedRequests[i])
request.options.Progress.IncrementRequests()
}
- wg.Wait()
+ spmHandler.Wait()
- return requestErr
+ if spmHandler.FoundFirstMatch() {
+ // ignore any context cancellation and in-transit execution errors
+ return nil
+ }
+ return multierr.Combine(spmHandler.CombinedResults()...)
}
// executeRaceRequest executes parallel requests for a template
func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicValues output.InternalEvent, callback protocols.OutputEventCallback) error {
- generator := request.newGenerator(false)
// Workers that keeps enqueuing new requests
maxWorkers := request.Threads
- swg := sizedwaitgroup.New(maxWorkers)
- var requestErr error
- mutex := &sync.Mutex{}
+ // Stop-at-first-match logic while executing requests
+ // parallely using threads
+ shouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)
+ spmHandler := httputils.NewBlockingSPMHandler[error](context.Background(), maxWorkers, shouldStop)
+ // wrappedCallback is a callback that wraps the original callback
+ // to implement stop at first match logic
+ wrappedCallback := func(event *output.InternalWrappedEvent) {
+ if !event.HasOperatorResult() {
+ callback(event) // not required but we can allow it
+ return
+ }
+ // this will execute match condition such that if stop at first match is enabled
+ // this will be only executed once
+ spmHandler.MatchCallback(func() {
+ callback(event)
+ })
+ if shouldStop {
+ // stop all running requests and exit
+ spmHandler.Trigger()
+ }
+ }
+
+ // iterate payloads and make requests
+ generator := request.newGenerator(false)
for {
inputData, payloads, ok := generator.nextValue()
if !ok {
@@ -151,24 +209,38 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
if input.MetaInput.Input == "" {
input.MetaInput.Input = generatedHttpRequest.URL()
}
- swg.Add()
+ spmHandler.Acquire()
go func(httpRequest *generatedRequest) {
- defer swg.Done()
-
- request.options.RateLimiter.Take()
-
- previous := make(map[string]interface{})
- err := request.executeRequest(input, httpRequest, previous, false, callback, 0)
- mutex.Lock()
- if err != nil {
- requestErr = multierr.Append(requestErr, err)
+ defer spmHandler.Release()
+ defer func() {
+ if r := recover(); r != nil {
+ gologger.Verbose().Msgf("[%s] Recovered from panic: %v\n", request.options.TemplateID, r)
+ }
+ }()
+ if spmHandler.FoundFirstMatch() {
+ return
+ }
+
+ select {
+ case <-spmHandler.Done():
+ return
+ case spmHandler.ResultChan <- func() error {
+ // putting ratelimiter here prevents any unnecessary waiting if any
+ request.options.RateLimiter.Take()
+ previous := make(map[string]interface{})
+ return request.executeRequest(input, httpRequest, previous, false, wrappedCallback, 0)
+ }():
+ return
}
- mutex.Unlock()
}(generatedHttpRequest)
request.options.Progress.IncrementRequests()
}
- swg.Wait()
- return requestErr
+ spmHandler.Wait()
+ if spmHandler.FoundFirstMatch() {
+ // ignore any context cancellation and in-transit execution errors
+ return nil
+ }
+ return multierr.Combine(spmHandler.CombinedResults()...)
}
// executeTurboHTTP executes turbo http request for a URL
@@ -198,10 +270,31 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu
if pipeOptions.MaxPendingRequests > maxWorkers {
maxWorkers = pipeOptions.MaxPendingRequests
}
- swg := sizedwaitgroup.New(maxWorkers)
- var requestErr error
- mutex := &sync.Mutex{}
+ // Stop-at-first-match logic while executing requests
+ // parallely using threads
+ // Stop-at-first-match logic while executing requests
+ // parallely using threads
+ shouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)
+ spmHandler := httputils.NewBlockingSPMHandler[error](context.Background(), maxWorkers, shouldStop)
+ // wrappedCallback is a callback that wraps the original callback
+ // to implement stop at first match logic
+ wrappedCallback := func(event *output.InternalWrappedEvent) {
+ if !event.HasOperatorResult() {
+ callback(event) // not required but we can allow it
+ return
+ }
+ // this will execute match condition such that if stop at first match is enabled
+ // this will be only executed once
+ spmHandler.MatchCallback(func() {
+ callback(event)
+ })
+ if shouldStop {
+ // stop all running requests and exit
+ spmHandler.Trigger()
+ }
+ }
+
for {
inputData, payloads, ok := generator.nextValue()
if !ok {
@@ -217,113 +310,34 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu
input.MetaInput.Input = generatedHttpRequest.URL()
}
generatedHttpRequest.pipelinedClient = pipeClient
- swg.Add()
+ spmHandler.Acquire()
go func(httpRequest *generatedRequest) {
- defer swg.Done()
-
- err := request.executeRequest(input, httpRequest, previous, false, callback, 0)
- mutex.Lock()
- if err != nil {
- requestErr = multierr.Append(requestErr, err)
- }
- mutex.Unlock()
- }(generatedHttpRequest)
- request.options.Progress.IncrementRequests()
- }
- swg.Wait()
- return requestErr
-}
-
-// executeFuzzingRule executes fuzzing request for a URL
-func (request *Request) executeFuzzingRule(input *contextargs.Context, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
- // If request is self-contained we don't need to parse any input.
- if !request.SelfContained {
- // If it's not self-contained we parse user provided input
- if _, err := urlutil.Parse(input.MetaInput.Input); err != nil {
- return errors.Wrap(err, "could not parse url")
- }
- }
- fuzzRequestCallback := func(gr fuzz.GeneratedRequest) bool {
- hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)
- hasInteractMarkers := len(gr.InteractURLs) > 0
- if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(input.MetaInput.Input) {
- return false
- }
- request.options.RateLimiter.Take()
- req := &generatedRequest{
- request: gr.Request,
- dynamicValues: gr.DynamicValues,
- interactshURLs: gr.InteractURLs,
- original: request,
- }
- var gotMatches bool
- requestErr := request.executeRequest(input, req, gr.DynamicValues, hasInteractMatchers, func(event *output.InternalWrappedEvent) {
- if hasInteractMarkers && hasInteractMatchers && request.options.Interactsh != nil {
- requestData := &interactsh.RequestData{
- MakeResultFunc: request.MakeResultEvent,
- Event: event,
- Operators: request.CompiledOperators,
- MatchFunc: request.Match,
- ExtractFunc: request.Extract,
+ defer spmHandler.Release()
+ defer func() {
+ if r := recover(); r != nil {
+ gologger.Verbose().Msgf("[%s] Recovered from panic: %v\n", request.options.TemplateID, r)
}
- request.options.Interactsh.RequestEvent(gr.InteractURLs, requestData)
- gotMatches = request.options.Interactsh.AlreadyMatched(requestData)
- } else {
- callback(event)
- }
- // Add the extracts to the dynamic values if any.
- if event.OperatorsResult != nil {
- gotMatches = event.OperatorsResult.Matched
+ }()
+ if spmHandler.FoundFirstMatch() {
+ // skip if first match is found
+ return
}
- }, 0)
- // If a variable is unresolved, skip all further requests
- if errors.Is(requestErr, errStopExecution) {
- return false
- }
- if requestErr != nil {
- if request.options.HostErrorsCache != nil {
- request.options.HostErrorsCache.MarkFailed(input.MetaInput.Input, requestErr)
+
+ select {
+ case <-spmHandler.Done():
+ return
+ case spmHandler.ResultChan <- request.executeRequest(input, httpRequest, previous, false, wrappedCallback, 0):
+ return
}
- gologger.Verbose().Msgf("[%s] Error occurred in request: %s\n", request.options.TemplateID, requestErr)
- }
+ }(generatedHttpRequest)
request.options.Progress.IncrementRequests()
-
- // 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 false
- }
- return true
}
-
- // Iterate through all requests for template and queue them for fuzzing
- generator := request.newGenerator(true)
- for {
- value, payloads, result := generator.nextValue()
- if !result {
- break
- }
- generated, err := generator.Make(context.Background(), input, value, payloads, nil)
- if err != nil {
- continue
- }
- input.MetaInput = &contextargs.MetaInput{Input: generated.URL()}
- for _, rule := range request.Fuzzing {
- err = rule.Execute(&fuzz.ExecuteRuleInput{
- Input: input,
- Callback: fuzzRequestCallback,
- Values: generated.dynamicValues,
- BaseRequest: generated.request,
- })
- if err == types.ErrNoMoreRequests {
- return nil
- }
- if err != nil {
- return errors.Wrap(err, "could not execute rule")
- }
- }
+ spmHandler.Wait()
+ if spmHandler.FoundFirstMatch() {
+ // ignore any context cancellation and in-transit execution errors
+ return nil
}
- return nil
+ return multierr.Combine(spmHandler.CombinedResults()...)
}
// ExecuteWithResults executes the final request on a URL
@@ -336,7 +350,6 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
if request.Pipeline {
return request.executeTurboHTTP(input, dynamicValues, previous, callback)
}
-
// verify if a basic race condition was requested
if request.Race && request.RaceNumberRequests > 0 {
return request.executeRaceRequest(input, dynamicValues, callback)
@@ -348,7 +361,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
}
// verify if parallel elaboration was requested
- if request.Threads > 0 {
+ if request.Threads > 0 && len(request.Payloads) > 0 {
return request.executeParallelHTTP(input, dynamicValues, callback)
}
@@ -389,7 +402,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
return true, nil
}
var gotMatches bool
- err = request.executeRequest(input, generatedHttpRequest, previous, hasInteractMatchers, func(event *output.InternalWrappedEvent) {
+ execReqErr := request.executeRequest(input, generatedHttpRequest, previous, hasInteractMatchers, func(event *output.InternalWrappedEvent) {
// a special case where operators has interactsh matchers and multiple request are made
// ex: status_code_2 , interactsh_protocol (from 1st request) etc
needsRequestEvent := interactsh.HasMatchers(request.CompiledOperators) && request.NeedsRequestCondition()
@@ -420,14 +433,14 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
}, generator.currentIndex)
// If a variable is unresolved, skip all further requests
- if errors.Is(err, errStopExecution) {
+ if errors.Is(execReqErr, errStopExecution) {
return true, nil
}
- if err != nil {
+ if execReqErr != nil {
if request.options.HostErrorsCache != nil {
request.options.HostErrorsCache.MarkFailed(input.MetaInput.ID(), err)
}
- requestErr = err
+ requestErr = errorutil.NewWithErr(execReqErr).Msgf("got err while executing %v", generatedHttpRequest.URL())
}
request.options.Progress.IncrementRequests()
@@ -549,6 +562,12 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
}
}
}
+
+ // === apply auth strategies ===
+ if generatedRequest.request != nil {
+ generatedRequest.ApplyAuth(request.options.AuthProvider)
+ }
+
var formedURL string
var hostname string
timeStart := time.Now()
@@ -652,10 +671,7 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
}
}
}
- // global wrap response body reader
- if resp != nil && resp.Body != nil {
- resp.Body = protocolutil.NewLimitResponseBody(resp.Body)
- }
+
if err != nil {
// rawhttp doesn't support draining response bodies.
if resp != nil && resp.Body != nil && generatedRequest.rawRequest == nil && !generatedRequest.original.Pipeline {
@@ -701,17 +717,19 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
request.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err)
duration := time.Since(timeStart)
+
// define max body read limit
- maxBodylimit := -1 // stick to default 4MB
+ maxBodylimit := MaxBodyRead // 10MB
if request.MaxSize > 0 {
- maxBodylimit = request.MaxSize
- } else if request.options.Options.ResponseReadSize != 0 {
- maxBodylimit = request.options.Options.ResponseReadSize
+ maxBodylimit = int64(request.MaxSize)
+ }
+ if request.options.Options.ResponseReadSize != 0 {
+ maxBodylimit = int64(request.options.Options.ResponseReadSize)
}
// respChain is http response chain that reads response body
// efficiently by reusing buffers and does all decoding and optimizations
- respChain := httputils.NewResponseChain(resp, int64(maxBodylimit))
+ respChain := httpUtils.NewResponseChain(resp, maxBodylimit)
defer respChain.Close() // reuse buffers
// we only intend to log/save the final redirected response
diff --git a/pkg/protocols/http/request_fuzz.go b/pkg/protocols/http/request_fuzz.go
new file mode 100644
index 0000000000..b9f22131e4
--- /dev/null
+++ b/pkg/protocols/http/request_fuzz.go
@@ -0,0 +1,305 @@
+package http
+
+// === Fuzzing Documentation (Scoped to this File) =====
+// -> request.executeFuzzingRule [iterates over payloads(+requests) and executes]
+// -> request.executePayloadUsingRules [executes single payload on all rules (if more than 1)]
+// -> request.executeGeneratedFuzzingRequest [execute final generated fuzzing request and get result]
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/nuclei/v3/pkg/fuzz"
+ "github.com/projectdiscovery/nuclei/v3/pkg/operators"
+ "github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
+ "github.com/projectdiscovery/nuclei/v3/pkg/output"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
+ "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
+ protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
+ "github.com/projectdiscovery/nuclei/v3/pkg/types"
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+// executeFuzzingRule executes fuzzing request for a URL
+// TODO:
+// 1. use SPMHandler and rewrite stop at first match logic here
+// 2. use scanContext instead of contextargs.Context
+func (request *Request) executeFuzzingRule(input *contextargs.Context, _ output.InternalEvent, callback protocols.OutputEventCallback) error {
+ // methdology:
+ // to check applicablity of rule, we first try to execute it with one value
+ // if it is applicable, we execute all requests
+ // if it is not applicable, we log and fail silently
+
+ // check if target should be fuzzed or not
+ if !request.ShouldFuzzTarget(input) {
+ urlx, _ := input.MetaInput.URL()
+ if urlx != nil {
+ gologger.Verbose().Msgf("[%s] fuzz: target(%s) not applicable for fuzzing\n", request.options.TemplateID, urlx.String())
+ } else {
+ gologger.Verbose().Msgf("[%s] fuzz: target(%s) not applicable for fuzzing\n", request.options.TemplateID, input.MetaInput.Input)
+ }
+ return nil
+ }
+
+ // Iterate through all requests for template and queue them for fuzzing
+ generator := request.newGenerator(true)
+
+ // this will generate next value along with request it is meant to be used with
+ currRequest, payloads, result := generator.nextValue()
+ if !result && input.MetaInput.ReqResp == nil {
+ // this case is only true if input is not a full http request
+ return fmt.Errorf("no values to generate requests")
+ }
+
+ // if it is a full http request obtained from target file
+ if input.MetaInput.ReqResp != nil {
+ // Note: in case of full http request, we only need to build it once
+ // and then reuse it for all requests and completely abandon the request
+ // returned by generator
+ _ = currRequest
+ generated, err := input.MetaInput.ReqResp.BuildRequest()
+ if err != nil {
+ return errors.Wrap(err, "fuzz: could not build request obtained from target file")
+ }
+ input.MetaInput.Input = generated.URL.String()
+ // execute with one value first to checks its applicability
+ err = request.executePayloadUsingRules(input, payloads, generated, callback)
+ if err != nil {
+ // in case of any error, return it
+ if fuzz.IsErrRuleNotApplicable(err) {
+ // log and fail silently
+ gologger.Verbose().Msgf("[%s] fuzz: %s\n", request.options.TemplateID, err)
+ return nil
+ }
+ if errors.Is(err, errStopExecution) {
+ return err
+ }
+ gologger.Verbose().Msgf("[%s] fuzz: inital payload request execution failed : %s\n", request.options.TemplateID, err)
+ }
+
+ // if it is applicable, execute all requests
+ for {
+ _, payloads, result := generator.nextValue()
+ if !result {
+ break
+ }
+ err = request.executePayloadUsingRules(input, payloads, generated, callback)
+ if err != nil {
+ // continue to next request since this is payload specific
+ gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed : %s\n", request.options.TemplateID, err)
+ continue
+ }
+ }
+ return nil
+ }
+
+ // ==== fuzzing when only URL is provided =====
+
+ generated, err := generator.Make(context.Background(), input, currRequest, payloads, nil)
+ if err != nil {
+ return errors.Wrap(err, "fuzz: could not build request from url")
+ }
+ // we need to use this url instead of input
+ inputx := input.Clone()
+ inputx.MetaInput.Input = generated.request.URL.String()
+ // execute with one value first to checks its applicability
+ err = request.executePayloadUsingRules(inputx, generated.dynamicValues, generated.request, callback)
+ if err != nil {
+ // in case of any error, return it
+ if fuzz.IsErrRuleNotApplicable(err) {
+ // log and fail silently
+ gologger.Verbose().Msgf("[%s] fuzz: rule not applicable : %s\n", request.options.TemplateID, err)
+ return nil
+ }
+ if errors.Is(err, errStopExecution) {
+ return err
+ }
+ gologger.Verbose().Msgf("[%s] fuzz: inital payload request execution failed : %s\n", request.options.TemplateID, err)
+ }
+
+ // continue to next request since this is payload specific
+ for {
+ currRequest, payloads, result = generator.nextValue()
+ if !result {
+ break
+ }
+ generated, err := generator.Make(context.Background(), input, currRequest, payloads, nil)
+ if err != nil {
+ return errors.Wrap(err, "fuzz: could not build request from url")
+ }
+ // we need to use this url instead of input
+ inputx := input.Clone()
+ inputx.MetaInput.Input = generated.request.URL.String()
+ // execute with one value first to checks its applicability
+ err = request.executePayloadUsingRules(inputx, generated.dynamicValues, generated.request, callback)
+ if err != nil {
+ gologger.Verbose().Msgf("[%s] fuzz: payload request execution failed : %s\n", request.options.TemplateID, err)
+ continue
+ }
+ }
+ return nil
+}
+
+// executePayloadUsingRules executes a payload using rules with given payload i.e values
+func (request *Request) executePayloadUsingRules(input *contextargs.Context, values map[string]interface{}, baseRequest *retryablehttp.Request, callback protocols.OutputEventCallback) error {
+ applicable := false
+ for _, rule := range request.Fuzzing {
+ err := rule.Execute(&fuzz.ExecuteRuleInput{
+ Input: input,
+ Callback: func(gr fuzz.GeneratedRequest) bool {
+ // TODO: replace this after scanContext Refactor
+ return request.executeGeneratedFuzzingRequest(gr, input, callback)
+ },
+ Values: values,
+ BaseRequest: baseRequest,
+ })
+ if err == nil {
+ applicable = true
+ continue
+ }
+ if fuzz.IsErrRuleNotApplicable(err) {
+ continue
+ }
+ if err == types.ErrNoMoreRequests {
+ return nil
+ }
+ return errors.Wrap(err, "could not execute rule")
+ }
+
+ if !applicable {
+ return fuzz.ErrRuleNotApplicable.Msgf(fmt.Sprintf("no rule was applicable for this request: %v", input.MetaInput.Input))
+ }
+ return nil
+}
+
+// executeGeneratedFuzzingRequest executes a generated fuzzing request after building it using rules and payloads
+func (request *Request) executeGeneratedFuzzingRequest(gr fuzz.GeneratedRequest, input *contextargs.Context, callback protocols.OutputEventCallback) bool {
+ hasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)
+ hasInteractMarkers := len(gr.InteractURLs) > 0
+ if request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(input.MetaInput.Input) {
+ return false
+ }
+ request.options.RateLimiter.Take()
+ req := &generatedRequest{
+ request: gr.Request,
+ dynamicValues: gr.DynamicValues,
+ interactshURLs: gr.InteractURLs,
+ original: request,
+ }
+ var gotMatches bool
+ requestErr := request.executeRequest(input, req, gr.DynamicValues, hasInteractMatchers, func(event *output.InternalWrappedEvent) {
+ if hasInteractMarkers && hasInteractMatchers && request.options.Interactsh != nil {
+ requestData := &interactsh.RequestData{
+ MakeResultFunc: request.MakeResultEvent,
+ Event: event,
+ Operators: request.CompiledOperators,
+ MatchFunc: request.Match,
+ ExtractFunc: request.Extract,
+ }
+ request.options.Interactsh.RequestEvent(gr.InteractURLs, requestData)
+ gotMatches = request.options.Interactsh.AlreadyMatched(requestData)
+ } else {
+ callback(event)
+ }
+ // Add the extracts to the dynamic values if any.
+ if event.OperatorsResult != nil {
+ gotMatches = event.OperatorsResult.Matched
+ }
+ }, 0)
+ // If a variable is unresolved, skip all further requests
+ if errors.Is(requestErr, errStopExecution) {
+ return false
+ }
+ if requestErr != nil {
+ if request.options.HostErrorsCache != nil {
+ request.options.HostErrorsCache.MarkFailed(input.MetaInput.Input, requestErr)
+ }
+ gologger.Verbose().Msgf("[%s] Error occurred in request: %s\n", request.options.TemplateID, requestErr)
+ }
+ request.options.Progress.IncrementRequests()
+
+ // 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 false
+ }
+ return true
+}
+
+// ShouldFuzzTarget checks if given target should be fuzzed or not using `filter` field in template
+func (request *Request) ShouldFuzzTarget(input *contextargs.Context) bool {
+ if len(request.FuzzingFilter) == 0 {
+ return true
+ }
+ status := []bool{}
+ for index, filter := range request.FuzzingFilter {
+ isMatch, _ := request.Match(request.filterDataMap(input), filter)
+ status = append(status, isMatch)
+ if request.options.Options.MatcherStatus {
+ gologger.Debug().Msgf("[%s] [%s] Filter => %s : %v", input.MetaInput.Target(), request.options.TemplateID, operators.GetMatcherName(filter, index), isMatch)
+ }
+ }
+ if len(status) == 0 {
+ return true
+ }
+ var matched bool
+ if request.fuzzingFilterCondition == matchers.ANDCondition {
+ matched = operators.EvalBoolSlice(status, true)
+ } else {
+ matched = operators.EvalBoolSlice(status, false)
+ }
+ if request.options.Options.MatcherStatus {
+ gologger.Debug().Msgf("[%s] [%s] Final Filter Status => %v", input.MetaInput.Target(), request.options.TemplateID, matched)
+ }
+ return matched
+}
+
+// input data map returns map[string]interface{} from input
+func (request *Request) filterDataMap(input *contextargs.Context) map[string]interface{} {
+ m := make(map[string]interface{})
+ parsed, err := input.MetaInput.URL()
+ if err != nil {
+ m["host"] = input.MetaInput.Input
+ return m
+ }
+ m = protocolutils.GenerateVariables(parsed, true, m)
+ for k, v := range m {
+ m[strings.ToLower(k)] = v
+ }
+ m["path"] = parsed.Path // override existing
+ m["query"] = parsed.RawQuery
+ // add request data like headers, body etc
+ if input.MetaInput.ReqResp != nil && input.MetaInput.ReqResp.Request != nil {
+ req := input.MetaInput.ReqResp.Request
+ m["method"] = req.Method
+ m["body"] = req.Body
+
+ sb := &strings.Builder{}
+ req.Headers.Iterate(func(k, v string) bool {
+ k = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(k), "-", "_"))
+ if strings.EqualFold(k, "Cookie") {
+ m["cookie"] = v
+ }
+ if strings.EqualFold(k, "User_Agent") {
+ m["user_agent"] = v
+ }
+ if strings.EqualFold(k, "content_type") {
+ m["content_type"] = v
+ }
+ sb.WriteString(fmt.Sprintf("%s: %s\n", k, v))
+ return true
+ })
+ m["header"] = sb.String()
+ }
+
+ // dump if svd is enabled
+ if request.options.Options.ShowVarDump {
+ gologger.Debug().Msgf("Fuzz Filter Variables: \n%s\n", vardump.DumpVariables(m))
+ }
+ return m
+}
diff --git a/pkg/protocols/http/utils.go b/pkg/protocols/http/utils.go
index fe893ebe60..875faaf21d 100644
--- a/pkg/protocols/http/utils.go
+++ b/pkg/protocols/http/utils.go
@@ -6,13 +6,22 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/rawhttp"
+ errorutil "github.com/projectdiscovery/utils/errors"
)
// dump creates a dump of the http request in form of a byte slice
func dump(req *generatedRequest, reqURL string) ([]byte, error) {
if req.request != nil {
- return req.request.Dump()
+ bin, err := req.request.Dump()
+ if err != nil {
+ return nil, errorutil.NewWithErr(err).WithTag("http").Msgf("could not dump request: %v", req.request.URL.String())
+ }
+ return bin, nil
}
rawHttpOptions := &rawhttp.Options{CustomHeaders: req.rawRequest.UnsafeHeaders, CustomRawBytes: req.rawRequest.UnsafeRawBytes}
- return rawhttp.DumpRequestRaw(req.rawRequest.Method, reqURL, req.rawRequest.Path, generators.ExpandMapValues(req.rawRequest.Headers), io.NopCloser(strings.NewReader(req.rawRequest.Data)), rawHttpOptions)
+ bin, err := rawhttp.DumpRequestRaw(req.rawRequest.Method, reqURL, req.rawRequest.Path, generators.ExpandMapValues(req.rawRequest.Headers), io.NopCloser(strings.NewReader(req.rawRequest.Data)), rawHttpOptions)
+ if err != nil {
+ return nil, errorutil.NewWithErr(err).WithTag("http").Msgf("could not dump request: %v", reqURL)
+ }
+ return bin, nil
}
diff --git a/pkg/protocols/javascript/js.go b/pkg/protocols/javascript/js.go
index 3ca587e495..fc32486937 100644
--- a/pkg/protocols/javascript/js.go
+++ b/pkg/protocols/javascript/js.go
@@ -447,7 +447,7 @@ func (request *Request) executeRequestParallel(ctxParent context.Context, hostPo
}
}
-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 {
+func (request *Request) executeRequestWithPayloads(hostPort string, input *contextargs.Context, _ 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 {
@@ -580,7 +580,7 @@ func (request *Request) getArgsCopy(input *contextargs.Context, payloadValues ma
}
// 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) {
+func (request *Request) evaluateArgs(payloadValues map[string]interface{}, _ *protocols.ExecutorOptions, ignoreErrors bool) (map[string]interface{}, error) {
argsCopy := make(map[string]interface{})
mainLoop:
for k, v := range request.Args {
diff --git a/pkg/protocols/network/request.go b/pkg/protocols/network/request.go
index 98f7ad9213..01991c0459 100644
--- a/pkg/protocols/network/request.go
+++ b/pkg/protocols/network/request.go
@@ -336,7 +336,7 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac
final, err := ConnReadNWithTimeout(conn, int64(bufferSize), DefaultReadTimeout)
if err != nil {
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
- return errors.Wrap(err, "could not read from server")
+ gologger.Verbose().Msgf("could not read more data from %s: %s", actualAddress, err)
}
responseBuilder.Write(final)
diff --git a/pkg/protocols/protocols.go b/pkg/protocols/protocols.go
index f2f814cc1b..4faa9610e8 100644
--- a/pkg/protocols/protocols.go
+++ b/pkg/protocols/protocols.go
@@ -10,6 +10,7 @@ import (
"github.com/logrusorgru/aurora"
+ "github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/input"
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
@@ -116,6 +117,10 @@ type ExecutorOptions struct {
// based on given logic. by default nuclei reverts to using value of `-c` when threads count
// is not specified or is 0 in template
OverrideThreadsCount PayloadThreadSetterCallback
+ // AuthProvider is a provider for auth strategies
+ AuthProvider authprovider.AuthProvider
+ //TemporaryDirectory is the directory to store temporary files
+ TemporaryDirectory string
}
// GetThreadsForPayloadRequests returns the number of threads to use as default for
@@ -124,13 +129,11 @@ func (e *ExecutorOptions) GetThreadsForNPayloadRequests(totalRequests int, curre
if e.OverrideThreadsCount != nil {
return e.OverrideThreadsCount(e, totalRequests, currentThreads)
}
- if currentThreads != 0 {
+ if currentThreads > 0 {
return currentThreads
+ } else {
+ return e.Options.PayloadConcurrency
}
- if totalRequests <= 0 {
- return e.Options.TemplateThreads
- }
- return totalRequests
}
// CreateTemplateCtxStore creates template context store (which contains templateCtx for every scan)
diff --git a/pkg/protocols/utils/reader.go b/pkg/protocols/utils/reader.go
deleted file mode 100644
index e6ed52530b..0000000000
--- a/pkg/protocols/utils/reader.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package utils
-
-import (
- "io"
-)
-
-var (
- MaxBodyRead = int64(1 << 22) // 4MB using shift operator
-)
-
-var _ io.ReadCloser = &LimitResponseBody{}
-
-type LimitResponseBody struct {
- io.Reader
- io.Closer
-}
-
-// NewLimitResponseBody wraps response body with a limit reader.
-// thus only allowing MaxBodyRead bytes to be read. i.e 4MB
-func NewLimitResponseBody(body io.ReadCloser) io.ReadCloser {
- return NewLimitResponseBodyWithSize(body, MaxBodyRead)
-}
-
-// NewLimitResponseBody wraps response body with a limit reader.
-// thus only allowing MaxBodyRead bytes to be read. i.e 4MB
-func NewLimitResponseBodyWithSize(body io.ReadCloser, size int64) io.ReadCloser {
- if body == nil {
- return nil
- }
- if size == -1 {
- // stick to default 4MB
- size = MaxBodyRead
- }
- return &LimitResponseBody{
- Reader: io.LimitReader(body, size),
- Closer: body,
- }
-}
-
-// LimitBodyRead limits the body read to MaxBodyRead bytes.
-func LimitBodyRead(r io.Reader) ([]byte, error) {
- return io.ReadAll(io.LimitReader(r, MaxBodyRead))
-}
diff --git a/pkg/reporting/client.go b/pkg/reporting/client.go
index 06b480d719..ce196f4ef3 100644
--- a/pkg/reporting/client.go
+++ b/pkg/reporting/client.go
@@ -11,5 +11,6 @@ type Client interface {
Close()
Clear()
CreateIssue(event *output.ResultEvent) error
+ CloseIssue(event *output.ResultEvent) error
GetReportingOptions() *Options
}
diff --git a/pkg/reporting/format/format_utils.go b/pkg/reporting/format/format_utils.go
index 253742b37d..62a3d75fe8 100644
--- a/pkg/reporting/format/format_utils.go
+++ b/pkg/reporting/format/format_utils.go
@@ -34,6 +34,13 @@ func GetMatchedTemplateName(event *output.ResultEvent) string {
return matchedTemplateName
}
+type reportMetadataEditorHook func(event *output.ResultEvent, formatter ResultFormatter) string
+
+var (
+ // ReportGenerationMetadataHooks are the hooks for adding metadata to the report
+ ReportGenerationMetadataHooks []reportMetadataEditorHook
+)
+
func CreateReportDescription(event *output.ResultEvent, formatter ResultFormatter, omitRaw bool) string {
template := GetMatchedTemplateName(event)
builder := &bytes.Buffer{}
@@ -47,6 +54,12 @@ func CreateReportDescription(event *output.ResultEvent, formatter ResultFormatte
builder.WriteString(fmt.Sprintf("%s: %s\n\n", formatter.MakeBold(key), types.ToString(data)))
})
+ if len(ReportGenerationMetadataHooks) > 0 {
+ for _, hook := range ReportGenerationMetadataHooks {
+ builder.WriteString(hook(event, formatter))
+ }
+ }
+
builder.WriteString(formatter.MakeBold("Template Information"))
builder.WriteString("\n\n")
builder.WriteString(CreateTemplateInfoTable(&event.Info, formatter))
diff --git a/pkg/reporting/options.go b/pkg/reporting/options.go
index 19762c8662..06a749d658 100644
--- a/pkg/reporting/options.go
+++ b/pkg/reporting/options.go
@@ -7,6 +7,8 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/sarif"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/splunk"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitea"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/github"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitlab"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/jira"
@@ -16,13 +18,15 @@ import (
// Options is a configuration file for nuclei reporting module
type Options struct {
// AllowList contains a list of allowed events for reporting module
- AllowList *Filter `yaml:"allow-list"`
+ AllowList *filters.Filter `yaml:"allow-list"`
// DenyList contains a list of denied events for reporting module
- DenyList *Filter `yaml:"deny-list"`
+ DenyList *filters.Filter `yaml:"deny-list"`
// GitHub contains configuration options for GitHub Issue Tracker
GitHub *github.Options `yaml:"github"`
// GitLab contains configuration options for GitLab Issue Tracker
GitLab *gitlab.Options `yaml:"gitlab"`
+ // Gitea contains configuration options for Gitea Issue Tracker
+ Gitea *gitea.Options `yaml:"gitea"`
// Jira contains configuration options for Jira Issue Tracker
Jira *jira.Options `yaml:"jira"`
// MarkdownExporter contains configuration options for Markdown Exporter Module
diff --git a/pkg/reporting/reporting.go b/pkg/reporting/reporting.go
index 9902493fc6..c9fe0f4bab 100644
--- a/pkg/reporting/reporting.go
+++ b/pkg/reporting/reporting.go
@@ -1,8 +1,12 @@
package reporting
import (
+ "fmt"
"os"
+ "strings"
+ "sync/atomic"
+ "github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
json_exporter "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonexporter"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl"
@@ -12,7 +16,6 @@ import (
"errors"
- "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/dedupe"
@@ -20,61 +23,30 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/sarif"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/splunk"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitea"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/github"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitlab"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/jira"
errorutil "github.com/projectdiscovery/utils/errors"
fileutil "github.com/projectdiscovery/utils/file"
- sliceutil "github.com/projectdiscovery/utils/slice"
)
-// Filter filters the received event and decides whether to perform
-// reporting for it or not.
-type Filter struct {
- Severities severity.Severities `yaml:"severity"`
- Tags stringslice.StringSlice `yaml:"tags"`
-}
-
var (
ErrReportingClientCreation = errors.New("could not create reporting client")
ErrExportClientCreation = errors.New("could not create exporting client")
)
-// GetMatch returns true if a filter matches result event
-func (filter *Filter) GetMatch(event *output.ResultEvent) bool {
- return isSeverityMatch(event, filter) && isTagMatch(event, filter) // TODO revisit this
-}
-
-func isTagMatch(event *output.ResultEvent, filter *Filter) bool {
- filterTags := filter.Tags
- if filterTags.IsEmpty() {
- return true
- }
-
- tags := event.Info.Tags.ToSlice()
- for _, filterTag := range filterTags.ToSlice() {
- if sliceutil.Contains(tags, filterTag) {
- return true
- }
- }
-
- return false
-}
-
-func isSeverityMatch(event *output.ResultEvent, filter *Filter) bool {
- resultEventSeverity := event.Info.SeverityHolder.Severity // TODO review
-
- if len(filter.Severities) == 0 {
- return true
- }
-
- return sliceutil.Contains(filter.Severities, resultEventSeverity)
-}
-
// Tracker is an interface implemented by an issue tracker
type Tracker interface {
+ // Name returns the name of the tracker
+ Name() string
// CreateIssue creates an issue in the tracker
- CreateIssue(event *output.ResultEvent) error
+ CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error)
+ // CloseIssue closes an issue in the tracker
+ CloseIssue(event *output.ResultEvent) error
+ // ShouldFilter determines if the event should be filtered out
+ ShouldFilter(event *output.ResultEvent) bool
}
// Exporter is an interface implemented by an issue exporter
@@ -91,10 +63,17 @@ type ReportingClient struct {
exporters []Exporter
options *Options
dedupe *dedupe.Storage
+
+ stats map[string]*IssueTrackerStats
+}
+
+type IssueTrackerStats struct {
+ Created atomic.Int32
+ Failed atomic.Int32
}
// New creates a new nuclei issue tracker reporting client
-func New(options *Options, db string) (Client, error) {
+func New(options *Options, db string, doNotDedupe bool) (Client, error) {
client := &ReportingClient{options: options}
if options.GitHub != nil {
@@ -115,6 +94,15 @@ func New(options *Options, db string) (Client, error) {
}
client.trackers = append(client.trackers, tracker)
}
+ if options.Gitea != nil {
+ options.Gitea.HttpClient = options.HttpClient
+ options.Gitea.OmitRaw = options.OmitRaw
+ tracker, err := gitea.New(options.Gitea)
+ if err != nil {
+ return nil, errorutil.NewWithErr(err).Wrap(ErrReportingClientCreation)
+ }
+ client.trackers = append(client.trackers, tracker)
+ }
if options.Jira != nil {
options.Jira.HttpClient = options.HttpClient
options.Jira.OmitRaw = options.OmitRaw
@@ -169,6 +157,20 @@ func New(options *Options, db string) (Client, error) {
client.exporters = append(client.exporters, exporter)
}
+ if doNotDedupe {
+ return client, nil
+ }
+
+ client.stats = make(map[string]*IssueTrackerStats)
+ for _, tracker := range client.trackers {
+ trackerName := tracker.Name()
+
+ client.stats[trackerName] = &IssueTrackerStats{
+ Created: atomic.Int32{},
+ Failed: atomic.Int32{},
+ }
+ }
+
storage, err := dedupe.New(db)
if err != nil {
return nil, err
@@ -187,10 +189,11 @@ func CreateConfigIfNotExists() error {
values := stringslice.StringSlice{Value: []string{}}
options := &Options{
- AllowList: &Filter{Tags: values},
- DenyList: &Filter{Tags: values},
+ AllowList: &filters.Filter{Tags: values},
+ DenyList: &filters.Filter{Tags: values},
GitHub: &github.Options{},
GitLab: &gitlab.Options{},
+ Gitea: &gitea.Options{},
Jira: &jira.Options{},
MarkdownExporter: &markdown.Options{},
SarifExporter: &sarif.Options{},
@@ -221,7 +224,30 @@ func (c *ReportingClient) RegisterExporter(exporter Exporter) {
// Close closes the issue tracker reporting client
func (c *ReportingClient) Close() {
- c.dedupe.Close()
+ // If we have stats for the trackers, print them
+ if len(c.stats) > 0 {
+ for _, tracker := range c.trackers {
+ trackerName := tracker.Name()
+
+ if stats, ok := c.stats[trackerName]; ok {
+ created := stats.Created.Load()
+ if created == 0 {
+ continue
+ }
+ var msgBuilder strings.Builder
+ msgBuilder.WriteString(fmt.Sprintf("%d %s tickets created successfully", created, trackerName))
+ failed := stats.Failed.Load()
+ if failed > 0 {
+ msgBuilder.WriteString(fmt.Sprintf(", %d failed", failed))
+ }
+ gologger.Info().Msgf(msgBuilder.String())
+ }
+ }
+ }
+
+ if c.dedupe != nil {
+ c.dedupe.Close()
+ }
for _, exporter := range c.exporters {
exporter.Close()
}
@@ -229,6 +255,7 @@ func (c *ReportingClient) Close() {
// CreateIssue creates an issue in the tracker
func (c *ReportingClient) CreateIssue(event *output.ResultEvent) error {
+ // process global allow/deny list
if c.options.AllowList != nil && !c.options.AllowList.GetMatch(event) {
return nil
}
@@ -236,11 +263,37 @@ func (c *ReportingClient) CreateIssue(event *output.ResultEvent) error {
return nil
}
- unique, err := c.dedupe.Index(event)
+ var err error
+ unique := true
+ if c.dedupe != nil {
+ unique, err = c.dedupe.Index(event)
+ }
if unique {
+ event.IssueTrackers = make(map[string]output.IssueTrackerMetadata)
+
for _, tracker := range c.trackers {
- if trackerErr := tracker.CreateIssue(event); trackerErr != nil {
+ // process tracker specific allow/deny list
+ if tracker.ShouldFilter(event) {
+ continue
+ }
+ trackerName := tracker.Name()
+ stats, statsOk := c.stats[trackerName]
+
+ reportData, trackerErr := tracker.CreateIssue(event)
+ if trackerErr != nil {
+ if statsOk {
+ _ = stats.Failed.Add(1)
+ }
err = multierr.Append(err, trackerErr)
+ continue
+ }
+ if statsOk {
+ _ = stats.Created.Add(1)
+ }
+
+ event.IssueTrackers[tracker.Name()] = output.IssueTrackerMetadata{
+ IssueID: reportData.IssueID,
+ IssueURL: reportData.IssueURL,
}
}
for _, exporter := range c.exporters {
@@ -252,10 +305,25 @@ func (c *ReportingClient) CreateIssue(event *output.ResultEvent) error {
return err
}
+// CloseIssue closes an issue in the tracker
+func (c *ReportingClient) CloseIssue(event *output.ResultEvent) error {
+ for _, tracker := range c.trackers {
+ if tracker.ShouldFilter(event) {
+ continue
+ }
+ if err := tracker.CloseIssue(event); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
func (c *ReportingClient) GetReportingOptions() *Options {
return c.options
}
func (c *ReportingClient) Clear() {
- c.dedupe.Clear()
+ if c.dedupe != nil {
+ c.dedupe.Clear()
+ }
}
diff --git a/pkg/reporting/trackers/filters/filters.go b/pkg/reporting/trackers/filters/filters.go
new file mode 100644
index 0000000000..390bcc7abd
--- /dev/null
+++ b/pkg/reporting/trackers/filters/filters.go
@@ -0,0 +1,54 @@
+package filters
+
+import (
+ "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
+ "github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
+ "github.com/projectdiscovery/nuclei/v3/pkg/output"
+
+ sliceutil "github.com/projectdiscovery/utils/slice"
+)
+
+// CreateIssueResponse is a response to creating an issue
+// in a tracker
+type CreateIssueResponse struct {
+ IssueID string `json:"issue_id"`
+ IssueURL string `json:"issue_url"`
+}
+
+// Filter filters the received event and decides whether to perform
+// reporting for it or not.
+type Filter struct {
+ Severities severity.Severities `yaml:"severity"`
+ Tags stringslice.StringSlice `yaml:"tags"`
+}
+
+// GetMatch returns true if a filter matches result event
+func (filter *Filter) GetMatch(event *output.ResultEvent) bool {
+ return isSeverityMatch(event, filter) && isTagMatch(event, filter) // TODO revisit this
+}
+
+func isTagMatch(event *output.ResultEvent, filter *Filter) bool {
+ filterTags := filter.Tags
+ if filterTags.IsEmpty() {
+ return true
+ }
+
+ tags := event.Info.Tags.ToSlice()
+ for _, filterTag := range filterTags.ToSlice() {
+ if sliceutil.Contains(tags, filterTag) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func isSeverityMatch(event *output.ResultEvent, filter *Filter) bool {
+ resultEventSeverity := event.Info.SeverityHolder.Severity // TODO review
+
+ if len(filter.Severities) == 0 {
+ return true
+ }
+
+ return sliceutil.Contains(filter.Severities, resultEventSeverity)
+}
diff --git a/pkg/reporting/trackers/gitea/gitea.go b/pkg/reporting/trackers/gitea/gitea.go
new file mode 100644
index 0000000000..70fbe7fe51
--- /dev/null
+++ b/pkg/reporting/trackers/gitea/gitea.go
@@ -0,0 +1,214 @@
+package gitea
+
+import (
+ "fmt"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "code.gitea.io/sdk/gitea"
+ "github.com/pkg/errors"
+ "github.com/projectdiscovery/nuclei/v3/pkg/output"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+// Integration is a client for an issue tracker integration
+type Integration struct {
+ client *gitea.Client
+ options *Options
+}
+
+// Options contains the configuration options for gitea issue tracker client
+type Options struct {
+ // BaseURL (optional) is the self-hosted Gitea application url
+ BaseURL string `yaml:"base-url" validate:"omitempty,url"`
+ // Token is the token for gitea account.
+ Token string `yaml:"token" validate:"required"`
+ // ProjectOwner is the owner (user or org) of the repository.
+ ProjectOwner string `yaml:"project-owner" validate:"required"`
+ // ProjectName is the name of the repository.
+ ProjectName string `yaml:"project-name" validate:"required"`
+ // IssueLabel is the label of the created issue type
+ IssueLabel string `yaml:"issue-label"`
+ // SeverityAsLabel (optional) adds the severity as the label of the created
+ // issue.
+ SeverityAsLabel bool `yaml:"severity-as-label"`
+ // AllowList contains a list of allowed events for this tracker
+ AllowList *filters.Filter `yaml:"allow-list"`
+ // DenyList contains a list of denied events for this tracker
+ DenyList *filters.Filter `yaml:"deny-list"`
+ // DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest
+ DuplicateIssueCheck bool `yaml:"duplicate-issue-check" default:"false"`
+
+ HttpClient *retryablehttp.Client `yaml:"-"`
+ OmitRaw bool `yaml:"-"`
+}
+
+// New creates a new issue tracker integration client based on options.
+func New(options *Options) (*Integration, error) {
+
+ var opts []gitea.ClientOption
+ opts = append(opts, gitea.SetToken(options.Token))
+
+ if options.HttpClient != nil {
+ opts = append(opts, gitea.SetHTTPClient(options.HttpClient.HTTPClient))
+ }
+
+ var remote string
+ if options.BaseURL != "" {
+ parsed, err := url.Parse(options.BaseURL)
+ if err != nil {
+ return nil, errors.Wrap(err, "could not parse custom baseurl")
+ }
+ if !strings.HasSuffix(parsed.Path, "/") {
+ parsed.Path += "/"
+ }
+ remote = parsed.String()
+ } else {
+ remote = `https://gitea.com/`
+ }
+
+ git, err := gitea.NewClient(remote, opts...)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Integration{client: git, options: options}, nil
+}
+
+// CreateIssue creates an issue in the tracker
+func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {
+ summary := format.Summary(event)
+ description := format.CreateReportDescription(event, util.MarkdownFormatter{}, i.options.OmitRaw)
+
+ labels := []string{}
+ severityLabel := fmt.Sprintf("Severity: %s", event.Info.SeverityHolder.Severity.String())
+ if i.options.SeverityAsLabel && severityLabel != "" {
+ labels = append(labels, severityLabel)
+ }
+ if label := i.options.IssueLabel; label != "" {
+ labels = append(labels, label)
+ }
+ customLabels, err := i.getLabelIDsByNames(labels)
+ if err != nil {
+ return nil, err
+ }
+
+ var issue *gitea.Issue
+ if i.options.DuplicateIssueCheck {
+ issue, err = i.findIssueByTitle(summary)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if issue == nil {
+ createdIssue, _, err := i.client.CreateIssue(i.options.ProjectOwner, i.options.ProjectName, gitea.CreateIssueOption{
+ Title: summary,
+ Body: description,
+ Labels: customLabels,
+ })
+ if err != nil {
+ return nil, err
+ }
+ return &filters.CreateIssueResponse{
+ IssueID: strconv.FormatInt(createdIssue.Index, 10),
+ IssueURL: createdIssue.URL,
+ }, nil
+ }
+
+ _, _, err = i.client.CreateIssueComment(i.options.ProjectOwner, i.options.ProjectName, issue.Index, gitea.CreateIssueCommentOption{
+ Body: description,
+ })
+ if err != nil {
+ return nil, err
+ }
+ return &filters.CreateIssueResponse{
+ IssueID: strconv.FormatInt(issue.Index, 10),
+ IssueURL: issue.URL,
+ }, nil
+}
+
+func (i *Integration) CloseIssue(event *output.ResultEvent) error {
+ // TODO: Implement
+ return nil
+}
+
+// ShouldFilter determines if an issue should be logged to this tracker
+func (i *Integration) ShouldFilter(event *output.ResultEvent) bool {
+ if i.options.AllowList != nil && i.options.AllowList.GetMatch(event) {
+ return true
+ }
+
+ if i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {
+ return true
+ }
+
+ return false
+}
+
+func (i *Integration) findIssueByTitle(title string) (*gitea.Issue, error) {
+
+ issueList, _, err := i.client.ListRepoIssues(i.options.ProjectOwner, i.options.ProjectName, gitea.ListIssueOption{
+ State: "all",
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ for _, issue := range issueList {
+ if issue.Title == title {
+ return issue, nil
+ }
+ }
+
+ return nil, nil
+}
+
+func (i *Integration) getLabelIDsByNames(labels []string) ([]int64, error) {
+
+ var ids []int64
+
+ existingLabels, _, err := i.client.ListRepoLabels(i.options.ProjectOwner, i.options.ProjectName, gitea.ListLabelsOptions{
+ ListOptions: gitea.ListOptions{Page: -1},
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ getLabel := func(name string) int64 {
+ for _, existingLabel := range existingLabels {
+ if existingLabel.Name == name {
+ return existingLabel.ID
+ }
+ }
+ return -1
+ }
+
+ for _, label := range labels {
+ labelID := getLabel(label)
+ if labelID == -1 {
+ newLabel, _, err := i.client.CreateLabel(i.options.ProjectOwner, i.options.ProjectName, gitea.CreateLabelOption{
+ Name: label,
+ Color: `#00aabb`,
+ Description: label,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ ids = append(ids, newLabel.ID)
+ } else {
+ ids = append(ids, labelID)
+ }
+ }
+
+ return ids, nil
+}
+
+func (i *Integration) Name() string {
+ return "gitea"
+}
diff --git a/pkg/reporting/trackers/github/github.go b/pkg/reporting/trackers/github/github.go
index f099fb873b..102692e6ce 100644
--- a/pkg/reporting/trackers/github/github.go
+++ b/pkg/reporting/trackers/github/github.go
@@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/url"
+ "strconv"
"strings"
"github.com/google/go-github/github"
@@ -13,6 +14,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/retryablehttp-go"
"golang.org/x/oauth2"
@@ -41,6 +43,10 @@ type Options struct {
// SeverityAsLabel (optional) sends the severity as the label of the created
// issue.
SeverityAsLabel bool `yaml:"severity-as-label"`
+ // AllowList contains a list of allowed events for this tracker
+ AllowList *filters.Filter `yaml:"allow-list"`
+ // DenyList contains a list of denied events for this tracker
+ DenyList *filters.Filter `yaml:"deny-list"`
// DuplicateIssueCheck (optional) comments under existing finding issue
// instead of creating duplicates for subsequent runs.
DuplicateIssueCheck bool `yaml:"duplicate-issue-check"`
@@ -80,7 +86,7 @@ func New(options *Options) (*Integration, error) {
}
// CreateIssue creates an issue in the tracker
-func (i *Integration) CreateIssue(event *output.ResultEvent) (err error) {
+func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {
summary := format.Summary(event)
description := format.CreateReportDescription(event, util.MarkdownFormatter{}, i.options.OmitRaw)
labels := []string{}
@@ -94,11 +100,12 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) (err error) {
ctx := context.Background()
+ var err error
var existingIssue *github.Issue
if i.options.DuplicateIssueCheck {
existingIssue, err = i.findIssueByTitle(ctx, summary)
if err != nil && !errors.Is(err, io.EOF) {
- return err
+ return nil, err
}
}
@@ -109,15 +116,21 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) (err error) {
Labels: &labels,
Assignees: &[]string{i.options.Username},
}
- _, _, err = i.client.Issues.Create(ctx, i.options.Owner, i.options.ProjectName, req)
- return err
+ createdIssue, _, err := i.client.Issues.Create(ctx, i.options.Owner, i.options.ProjectName, req)
+ if err != nil {
+ return nil, err
+ }
+ return &filters.CreateIssueResponse{
+ IssueID: strconv.FormatInt(createdIssue.GetID(), 10),
+ IssueURL: createdIssue.GetHTMLURL(),
+ }, nil
} else {
if existingIssue.GetState() == "closed" {
stateOpen := "open"
if _, _, err := i.client.Issues.Edit(ctx, i.options.Owner, i.options.ProjectName, *existingIssue.Number, &github.IssueRequest{
State: &stateOpen,
}); err != nil {
- return fmt.Errorf("error reopening issue %d: %s", *existingIssue.Number, err)
+ return nil, fmt.Errorf("error reopening issue %d: %s", *existingIssue.Number, err)
}
}
@@ -125,8 +138,52 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) (err error) {
Body: &description,
}
_, _, err = i.client.Issues.CreateComment(ctx, i.options.Owner, i.options.ProjectName, *existingIssue.Number, req)
+ if err != nil {
+ return nil, err
+ }
+ return &filters.CreateIssueResponse{
+ IssueID: strconv.FormatInt(existingIssue.GetID(), 10),
+ IssueURL: existingIssue.GetHTMLURL(),
+ }, nil
+ }
+}
+
+func (i *Integration) CloseIssue(event *output.ResultEvent) error {
+ ctx := context.Background()
+ summary := format.Summary(event)
+
+ existingIssue, err := i.findIssueByTitle(ctx, summary)
+ if err != nil && !errors.Is(err, io.EOF) {
return err
}
+ if existingIssue == nil {
+ return nil
+ }
+
+ stateClosed := "closed"
+ if _, _, err := i.client.Issues.Edit(ctx, i.options.Owner, i.options.ProjectName, *existingIssue.Number, &github.IssueRequest{
+ State: &stateClosed,
+ }); err != nil {
+ return fmt.Errorf("error closing issue %d: %s", *existingIssue.Number, err)
+ }
+ return nil
+}
+
+func (i *Integration) Name() string {
+ return "github"
+}
+
+// ShouldFilter determines if an issue should be logged to this tracker
+func (i *Integration) ShouldFilter(event *output.ResultEvent) bool {
+ if i.options.AllowList != nil && i.options.AllowList.GetMatch(event) {
+ return true
+ }
+
+ if i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {
+ return true
+ }
+
+ return false
}
func (i *Integration) findIssueByTitle(ctx context.Context, title string) (*github.Issue, error) {
diff --git a/pkg/reporting/trackers/gitlab/gitlab.go b/pkg/reporting/trackers/gitlab/gitlab.go
index 22c191f576..dc5bef0d6e 100644
--- a/pkg/reporting/trackers/gitlab/gitlab.go
+++ b/pkg/reporting/trackers/gitlab/gitlab.go
@@ -2,12 +2,14 @@ package gitlab
import (
"fmt"
+ "strconv"
"github.com/xanzy/go-gitlab"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
"github.com/projectdiscovery/retryablehttp-go"
)
@@ -33,6 +35,10 @@ type Options struct {
// SeverityAsLabel (optional) sends the severity as the label of the created
// issue.
SeverityAsLabel bool `yaml:"severity-as-label"`
+ // AllowList contains a list of allowed events for this tracker
+ AllowList *filters.Filter `yaml:"allow-list"`
+ // DenyList contains a list of denied events for this tracker
+ DenyList *filters.Filter `yaml:"deny-list"`
// DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest
DuplicateIssueCheck bool `yaml:"duplicate-issue-check" default:"false"`
@@ -61,7 +67,7 @@ func New(options *Options) (*Integration, error) {
}
// CreateIssue creates an issue in the tracker
-func (i *Integration) CreateIssue(event *output.ResultEvent) error {
+func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {
summary := format.Summary(event)
description := format.CreateReportDescription(event, util.MarkdownFormatter{}, i.options.OmitRaw)
labels := []string{}
@@ -83,7 +89,7 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) error {
Search: &summary,
})
if err != nil {
- return err
+ return nil, err
}
if len(issues) > 0 {
issue := issues[0]
@@ -91,7 +97,7 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) error {
Body: &description,
})
if err != nil {
- return err
+ return nil, err
}
if issue.State == "closed" {
reopen := "reopen"
@@ -100,15 +106,71 @@ func (i *Integration) CreateIssue(event *output.ResultEvent) error {
})
fmt.Sprintln(resp, err)
}
- return err
+ if err != nil {
+ return nil, err
+ }
+ return &filters.CreateIssueResponse{
+ IssueID: strconv.FormatInt(int64(issue.ID), 10),
+ IssueURL: issue.WebURL,
+ }, nil
}
}
- _, _, err := i.client.Issues.CreateIssue(i.options.ProjectName, &gitlab.CreateIssueOptions{
+ createdIssue, _, err := i.client.Issues.CreateIssue(i.options.ProjectName, &gitlab.CreateIssueOptions{
Title: &summary,
Description: &description,
Labels: &customLabels,
AssigneeIDs: &assigneeIDs,
})
+ if err != nil {
+ return nil, err
+ }
+ return &filters.CreateIssueResponse{
+ IssueID: strconv.FormatInt(int64(createdIssue.ID), 10),
+ IssueURL: createdIssue.WebURL,
+ }, nil
+}
+
+func (i *Integration) Name() string {
+ return "gitlab"
+}
+
+func (i *Integration) CloseIssue(event *output.ResultEvent) error {
+ searchIn := "title"
+ searchState := "all"
+
+ summary := format.Summary(event)
+ issues, _, err := i.client.Issues.ListProjectIssues(i.options.ProjectName, &gitlab.ListProjectIssuesOptions{
+ In: &searchIn,
+ State: &searchState,
+ Search: &summary,
+ })
+ if err != nil {
+ return err
+ }
+ if len(issues) <= 0 {
+ return nil
+ }
+
+ issue := issues[0]
+ state := "close"
+ _, _, err = i.client.Issues.UpdateIssue(i.options.ProjectName, issue.IID, &gitlab.UpdateIssueOptions{
+ StateEvent: &state,
+ })
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// ShouldFilter determines if an issue should be logged to this tracker
+func (i *Integration) ShouldFilter(event *output.ResultEvent) bool {
+ if i.options.AllowList != nil && i.options.AllowList.GetMatch(event) {
+ return true
+ }
+
+ if i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {
+ return true
+ }
- return err
+ return false
}
diff --git a/pkg/reporting/trackers/jira/jira.go b/pkg/reporting/trackers/jira/jira.go
index 77302f5add..97ae078312 100644
--- a/pkg/reporting/trackers/jira/jira.go
+++ b/pkg/reporting/trackers/jira/jira.go
@@ -3,7 +3,9 @@ package jira
import (
"fmt"
"io"
+ "net/url"
"strings"
+ "sync"
"github.com/andygrunwald/go-jira"
"github.com/trivago/tgo/tcontainer"
@@ -12,6 +14,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
+ "github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
"github.com/projectdiscovery/retryablehttp-go"
)
@@ -46,6 +49,9 @@ type Integration struct {
Formatter
jira *jira.Client
options *Options
+
+ once *sync.Once
+ transitionID string
}
// Options contains the configuration options for jira client
@@ -69,6 +75,10 @@ type Options struct {
// SeverityAsLabel (optional) sends the severity as the label of the created
// issue.
SeverityAsLabel bool `yaml:"severity-as-label" json:"severity_as_label"`
+ // AllowList contains a list of allowed events for this tracker
+ AllowList *filters.Filter `yaml:"allow-list"`
+ // DenyList contains a list of denied events for this tracker
+ DenyList *filters.Filter `yaml:"deny-list"`
// Severity (optional) is the severity of the issue.
Severity []string `yaml:"severity" json:"severity"`
HttpClient *retryablehttp.Client `yaml:"-" json:"-"`
@@ -97,11 +107,20 @@ func New(options *Options) (*Integration, error) {
if err != nil {
return nil, err
}
- return &Integration{jira: jiraClient, options: options}, nil
+ integration := &Integration{
+ jira: jiraClient,
+ options: options,
+ once: &sync.Once{},
+ }
+ return integration, nil
+}
+
+func (i *Integration) Name() string {
+ return "jira"
}
// CreateNewIssue creates a new issue in the tracker
-func (i *Integration) CreateNewIssue(event *output.ResultEvent) error {
+func (i *Integration) CreateNewIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {
summary := format.Summary(event)
labels := []string{}
severityLabel := fmt.Sprintf("Severity:%s", event.Info.SeverityHolder.Severity.String())
@@ -122,7 +141,7 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) error {
for nestedName, nestedValue := range valueMap {
fmtNestedValue, ok := nestedValue.(string)
if !ok {
- return fmt.Errorf(`couldn't iterate on nested item "%s": %s`, nestedName, nestedValue)
+ return nil, fmt.Errorf(`couldn't iterate on nested item "%s": %s`, nestedName, nestedValue)
}
if strings.HasPrefix(fmtNestedValue, "$") {
nestedValue = strings.TrimPrefix(fmtNestedValue, "$")
@@ -155,8 +174,10 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) error {
}
}
fields := &jira.IssueFields{
+ Assignee: &jira.User{Name: i.options.AccountID},
Description: format.CreateReportDescription(event, i, i.options.OmitRaw),
Unknowns: customFields,
+ Labels: labels,
Type: jira.IssueType{Name: i.options.IssueType},
Project: jira.Project{Key: i.options.ProjectName},
Summary: summary,
@@ -177,36 +198,92 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) error {
issueData := &jira.Issue{
Fields: fields,
}
- _, resp, err := i.jira.Issue.Create(issueData)
+ createdIssue, resp, err := i.jira.Issue.Create(issueData)
if err != nil {
var data string
if resp != nil && resp.Body != nil {
d, _ := io.ReadAll(resp.Body)
data = string(d)
}
- return fmt.Errorf("%w => %s", err, data)
+ return nil, fmt.Errorf("%w => %s", err, data)
}
- return nil
+ return getIssueResponseFromJira(createdIssue)
+}
+
+func getIssueResponseFromJira(issue *jira.Issue) (*filters.CreateIssueResponse, error) {
+ parsed, err := url.Parse(issue.Self)
+ if err != nil {
+ return nil, err
+ }
+ parsed.Path = fmt.Sprintf("/browse/%s", issue.Key)
+ issueURL := parsed.String()
+
+ return &filters.CreateIssueResponse{
+ IssueID: issue.ID,
+ IssueURL: issueURL,
+ }, nil
}
// CreateIssue creates an issue in the tracker or updates the existing one
-func (i *Integration) CreateIssue(event *output.ResultEvent) error {
+func (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {
if i.options.UpdateExisting {
- issueID, err := i.FindExistingIssue(event)
+ issue, err := i.FindExistingIssue(event)
if err != nil {
- return err
- } else if issueID != "" {
- _, _, err = i.jira.Issue.AddComment(issueID, &jira.Comment{
+ return nil, err
+ } else if issue.ID != "" {
+ _, _, err = i.jira.Issue.AddComment(issue.ID, &jira.Comment{
Body: format.CreateReportDescription(event, i, i.options.OmitRaw),
})
- return err
+ if err != nil {
+ return nil, err
+ }
+ return getIssueResponseFromJira(&issue)
}
}
return i.CreateNewIssue(event)
}
+func (i *Integration) CloseIssue(event *output.ResultEvent) error {
+ if i.options.StatusNot == "" {
+ return nil
+ }
+
+ issue, err := i.FindExistingIssue(event)
+ if err != nil {
+ return err
+ } else if issue.ID != "" {
+ // Lazy load the transitions ID in case it's not set
+ i.once.Do(func() {
+ transitions, _, err := i.jira.Issue.GetTransitions(issue.ID)
+ if err != nil {
+ return
+ }
+ for _, transition := range transitions {
+ if transition.Name == i.options.StatusNot {
+ i.transitionID = transition.ID
+ break
+ }
+ }
+ })
+ if i.transitionID == "" {
+ return nil
+ }
+ transition := jira.CreateTransitionPayload{
+ Transition: jira.TransitionPayload{
+ ID: i.transitionID,
+ },
+ }
+
+ _, err = i.jira.Issue.DoTransitionWithPayload(issue.ID, transition)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// FindExistingIssue checks if the issue already exists and returns its ID
-func (i *Integration) FindExistingIssue(event *output.ResultEvent) (string, error) {
+func (i *Integration) FindExistingIssue(event *output.ResultEvent) (jira.Issue, error) {
template := format.GetMatchedTemplateName(event)
jql := fmt.Sprintf("summary ~ \"%s\" AND summary ~ \"%s\" AND status != \"%s\" AND project = \"%s\"", template, event.Host, i.options.StatusNot, i.options.ProjectName)
@@ -221,16 +298,29 @@ func (i *Integration) FindExistingIssue(event *output.ResultEvent) (string, erro
d, _ := io.ReadAll(resp.Body)
data = string(d)
}
- return "", fmt.Errorf("%w => %s", err, data)
+ return jira.Issue{}, fmt.Errorf("%w => %s", err, data)
}
switch resp.Total {
case 0:
- return "", nil
+ return jira.Issue{}, nil
case 1:
- return chunk[0].ID, nil
+ return chunk[0], nil
default:
gologger.Warning().Msgf("Discovered multiple opened issues %s for the host %s: The issue [%s] will be updated.", template, event.Host, chunk[0].ID)
- return chunk[0].ID, nil
+ return chunk[0], nil
}
}
+
+// ShouldFilter determines if an issue should be logged to this tracker
+func (i *Integration) ShouldFilter(event *output.ResultEvent) bool {
+ if i.options.AllowList != nil && i.options.AllowList.GetMatch(event) {
+ return true
+ }
+
+ if i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {
+ return true
+ }
+
+ return false
+}
diff --git a/pkg/templates/cache/cache.go b/pkg/templates/cache/cache.go
index 65e7d37f79..2b06a758a4 100644
--- a/pkg/templates/cache/cache.go
+++ b/pkg/templates/cache/cache.go
@@ -1,17 +1,17 @@
package cache
import (
- "sync"
+ mapsutil "github.com/projectdiscovery/utils/maps"
)
// Templates is a cache for caching and storing templates for reuse.
type Templates struct {
- items *sync.Map
+ items *mapsutil.SyncLockMap[string, parsedTemplateErrHolder]
}
// New returns a new templates cache
func New() *Templates {
- return &Templates{items: &sync.Map{}}
+ return &Templates{items: mapsutil.NewSyncLockMap[string, parsedTemplateErrHolder]()}
}
type parsedTemplateErrHolder struct {
@@ -22,18 +22,19 @@ type parsedTemplateErrHolder struct {
// Has returns true if the cache has a template. The template
// is returned along with any errors if found.
func (t *Templates) Has(template string) (interface{}, error) {
- value, ok := t.items.Load(template)
- if !ok || value == nil {
- return nil, nil
- }
- templateError, ok := value.(parsedTemplateErrHolder)
+ value, ok := t.items.Get(template)
if !ok {
return nil, nil
}
- return templateError.template, templateError.err
+ return value.template, value.err
}
// Store stores a template with data and error
func (t *Templates) Store(template string, data interface{}, err error) {
- t.items.Store(template, parsedTemplateErrHolder{template: data, err: err})
+ _ = t.items.Set(template, parsedTemplateErrHolder{template: data, err: err})
+}
+
+// Purge the cache
+func (t *Templates) Purge() {
+ t.items.Clear()
}
diff --git a/pkg/templates/compile.go b/pkg/templates/compile.go
index 3bffe7b9f5..67b4d4ea6a 100644
--- a/pkg/templates/compile.go
+++ b/pkg/templates/compile.go
@@ -45,6 +45,7 @@ func init() {
SignatureStats[verifier.Identifier()] = &atomic.Uint64{}
}
SignatureStats[Unsigned] = &atomic.Uint64{}
+ config.DefaultConfig.RegisterGlobalCache(parsedTemplatesCache)
}
// Parse parses a yaml request template file
diff --git a/pkg/templates/templates.go b/pkg/templates/templates.go
index c98f220eda..bd81d1451b 100644
--- a/pkg/templates/templates.go
+++ b/pkg/templates/templates.go
@@ -191,6 +191,29 @@ func (template *Template) Type() types.ProtocolType {
}
}
+// IsFuzzing returns true if the template is a fuzzing template
+func (template *Template) IsFuzzing() bool {
+ if len(template.RequestsHTTP) == 0 && len(template.RequestsHeadless) == 0 {
+ // fuzzing is only supported for http and headless protocols
+ return false
+ }
+ if len(template.RequestsHTTP) > 0 {
+ for _, request := range template.RequestsHTTP {
+ if len(request.Fuzzing) > 0 {
+ return true
+ }
+ }
+ }
+ if len(template.RequestsHeadless) > 0 {
+ for _, request := range template.RequestsHeadless {
+ if len(request.Fuzzing) > 0 {
+ return true
+ }
+ }
+ }
+ return false
+}
+
// HasCodeProtocol returns true if the template has a code protocol section
func (template *Template) HasCodeProtocol() bool {
return len(template.RequestsCode) > 0
@@ -371,6 +394,17 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err
}
}
+ // for javascript protocol code references
+ for _, request := range template.RequestsJavascript {
+ // simple test to check if source is a file or a snippet
+ if len(strings.Split(request.Code, "\n")) == 1 && fileutil.FileExists(request.Code) {
+ if val, ok := loadFile(request.Code); ok {
+ template.ImportedFiles = append(template.ImportedFiles, request.Code)
+ request.Code = val
+ }
+ }
+ }
+
// flow code references
if template.Flow != "" {
if len(template.Flow) > 0 && filepath.Ext(template.Flow) == ".js" && fileutil.FileExists(template.Flow) {
@@ -398,6 +432,20 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err
}
}
}
+
+ // for javascript protocol code references
+ for _, req := range template.RequestsQueue {
+ if req.Type() == types.JavascriptProtocol {
+ request := req.(*javascript.Request)
+ // simple test to check if source is a file or a snippet
+ if len(strings.Split(request.Code, "\n")) == 1 && fileutil.FileExists(request.Code) {
+ if val, ok := loadFile(request.Code); ok {
+ template.ImportedFiles = append(template.ImportedFiles, request.Code)
+ request.Code = val
+ }
+ }
+ }
+ }
}
return multierr.Combine(errs...)
diff --git a/pkg/templates/templates_doc.go b/pkg/templates/templates_doc.go
index 3241c4a885..cfa78e3ef5 100644
--- a/pkg/templates/templates_doc.go
+++ b/pkg/templates/templates_doc.go
@@ -19,7 +19,10 @@ var (
GENERATORSAttackTypeHolderDoc encoder.Doc
HTTPMethodTypeHolderDoc encoder.Doc
FUZZRuleDoc encoder.Doc
+ SliceOrMapSliceDoc encoder.Doc
SignatureTypeHolderDoc encoder.Doc
+ MATCHERSMatcherDoc encoder.Doc
+ MatcherTypeHolderDoc encoder.Doc
DNSRequestDoc encoder.Doc
DNSRequestTypeHolderDoc encoder.Doc
FILERequestDoc encoder.Doc
@@ -456,7 +459,7 @@ func init() {
Value: "HTTP response headers in name:value format",
},
}
- HTTPRequestDoc.Fields = make([]encoder.Doc, 32)
+ HTTPRequestDoc.Fields = make([]encoder.Doc, 35)
HTTPRequestDoc.Fields[0].Name = "path"
HTTPRequestDoc.Fields[0].Type = "[]string"
HTTPRequestDoc.Fields[0].Note = ""
@@ -562,89 +565,104 @@ func init() {
HTTPRequestDoc.Fields[15].Note = ""
HTTPRequestDoc.Fields[15].Description = "Fuzzing describes schema to fuzz http requests"
HTTPRequestDoc.Fields[15].Comments[encoder.LineComment] = " Fuzzing describes schema to fuzz http requests"
- HTTPRequestDoc.Fields[16].Name = "signature"
- HTTPRequestDoc.Fields[16].Type = "SignatureTypeHolder"
+ HTTPRequestDoc.Fields[16].Name = "self-contained"
+ HTTPRequestDoc.Fields[16].Type = "bool"
HTTPRequestDoc.Fields[16].Note = ""
- HTTPRequestDoc.Fields[16].Description = "Signature is the request signature method"
- HTTPRequestDoc.Fields[16].Comments[encoder.LineComment] = "Signature is the request signature method"
- HTTPRequestDoc.Fields[16].Values = []string{
+ HTTPRequestDoc.Fields[16].Description = "SelfContained specifies if the request is self-contained."
+ HTTPRequestDoc.Fields[16].Comments[encoder.LineComment] = "SelfContained specifies if the request is self-contained."
+ HTTPRequestDoc.Fields[17].Name = "signature"
+ HTTPRequestDoc.Fields[17].Type = "SignatureTypeHolder"
+ HTTPRequestDoc.Fields[17].Note = ""
+ HTTPRequestDoc.Fields[17].Description = "Signature is the request signature method"
+ HTTPRequestDoc.Fields[17].Comments[encoder.LineComment] = "Signature is the request signature method"
+ HTTPRequestDoc.Fields[17].Values = []string{
"AWS",
}
- HTTPRequestDoc.Fields[17].Name = "cookie-reuse"
- HTTPRequestDoc.Fields[17].Type = "bool"
- HTTPRequestDoc.Fields[17].Note = ""
- HTTPRequestDoc.Fields[17].Description = "CookieReuse is an optional setting that enables cookie reuse for\nall requests defined in raw section."
- HTTPRequestDoc.Fields[17].Comments[encoder.LineComment] = "CookieReuse is an optional setting that enables cookie reuse for"
- HTTPRequestDoc.Fields[18].Name = "disable-cookie"
+ HTTPRequestDoc.Fields[18].Name = "cookie-reuse"
HTTPRequestDoc.Fields[18].Type = "bool"
HTTPRequestDoc.Fields[18].Note = ""
- HTTPRequestDoc.Fields[18].Description = "DisableCookie is an optional setting that disables cookie reuse"
- HTTPRequestDoc.Fields[18].Comments[encoder.LineComment] = "DisableCookie is an optional setting that disables cookie reuse"
- HTTPRequestDoc.Fields[19].Name = "read-all"
+ HTTPRequestDoc.Fields[18].Description = "CookieReuse is an optional setting that enables cookie reuse for\nall requests defined in raw section."
+ HTTPRequestDoc.Fields[18].Comments[encoder.LineComment] = "CookieReuse is an optional setting that enables cookie reuse for"
+ HTTPRequestDoc.Fields[19].Name = "disable-cookie"
HTTPRequestDoc.Fields[19].Type = "bool"
HTTPRequestDoc.Fields[19].Note = ""
- HTTPRequestDoc.Fields[19].Description = "Enables force reading of the entire raw unsafe request body ignoring\nany specified content length headers."
- HTTPRequestDoc.Fields[19].Comments[encoder.LineComment] = "Enables force reading of the entire raw unsafe request body ignoring"
- HTTPRequestDoc.Fields[20].Name = "redirects"
+ HTTPRequestDoc.Fields[19].Description = "DisableCookie is an optional setting that disables cookie reuse"
+ HTTPRequestDoc.Fields[19].Comments[encoder.LineComment] = "DisableCookie is an optional setting that disables cookie reuse"
+ HTTPRequestDoc.Fields[20].Name = "read-all"
HTTPRequestDoc.Fields[20].Type = "bool"
HTTPRequestDoc.Fields[20].Note = ""
- HTTPRequestDoc.Fields[20].Description = "Redirects specifies whether redirects should be followed by the HTTP Client.\n\nThis can be used in conjunction with `max-redirects` to control the HTTP request redirects."
- HTTPRequestDoc.Fields[20].Comments[encoder.LineComment] = "Redirects specifies whether redirects should be followed by the HTTP Client."
- HTTPRequestDoc.Fields[21].Name = "host-redirects"
+ HTTPRequestDoc.Fields[20].Description = "Enables force reading of the entire raw unsafe request body ignoring\nany specified content length headers."
+ HTTPRequestDoc.Fields[20].Comments[encoder.LineComment] = "Enables force reading of the entire raw unsafe request body ignoring"
+ HTTPRequestDoc.Fields[21].Name = "redirects"
HTTPRequestDoc.Fields[21].Type = "bool"
HTTPRequestDoc.Fields[21].Note = ""
- HTTPRequestDoc.Fields[21].Description = "Redirects specifies whether only redirects to the same host should be followed by the HTTP Client.\n\nThis can be used in conjunction with `max-redirects` to control the HTTP request redirects."
- HTTPRequestDoc.Fields[21].Comments[encoder.LineComment] = "Redirects specifies whether only redirects to the same host should be followed by the HTTP Client."
- HTTPRequestDoc.Fields[22].Name = "pipeline"
+ HTTPRequestDoc.Fields[21].Description = "Redirects specifies whether redirects should be followed by the HTTP Client.\n\nThis can be used in conjunction with `max-redirects` to control the HTTP request redirects."
+ HTTPRequestDoc.Fields[21].Comments[encoder.LineComment] = "Redirects specifies whether redirects should be followed by the HTTP Client."
+ HTTPRequestDoc.Fields[22].Name = "host-redirects"
HTTPRequestDoc.Fields[22].Type = "bool"
HTTPRequestDoc.Fields[22].Note = ""
- HTTPRequestDoc.Fields[22].Description = "Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining\n\nAll requests must be idempotent (GET/POST). This can be used for race conditions/billions requests."
- HTTPRequestDoc.Fields[22].Comments[encoder.LineComment] = "Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining"
- HTTPRequestDoc.Fields[23].Name = "unsafe"
+ HTTPRequestDoc.Fields[22].Description = "Redirects specifies whether only redirects to the same host should be followed by the HTTP Client.\n\nThis can be used in conjunction with `max-redirects` to control the HTTP request redirects."
+ HTTPRequestDoc.Fields[22].Comments[encoder.LineComment] = "Redirects specifies whether only redirects to the same host should be followed by the HTTP Client."
+ HTTPRequestDoc.Fields[23].Name = "pipeline"
HTTPRequestDoc.Fields[23].Type = "bool"
HTTPRequestDoc.Fields[23].Note = ""
- HTTPRequestDoc.Fields[23].Description = "Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests.\n\nThis uses the [rawhttp](https://github.com/projectdiscovery/rawhttp) engine to achieve complete\ncontrol over the request, with no normalization performed by the client."
- HTTPRequestDoc.Fields[23].Comments[encoder.LineComment] = "Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests."
- HTTPRequestDoc.Fields[24].Name = "race"
+ HTTPRequestDoc.Fields[23].Description = "Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining\n\nAll requests must be idempotent (GET/POST). This can be used for race conditions/billions requests."
+ HTTPRequestDoc.Fields[23].Comments[encoder.LineComment] = "Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining"
+ HTTPRequestDoc.Fields[24].Name = "unsafe"
HTTPRequestDoc.Fields[24].Type = "bool"
HTTPRequestDoc.Fields[24].Note = ""
- HTTPRequestDoc.Fields[24].Description = "Race determines if all the request have to be attempted at the same time (Race Condition)\n\nThe actual number of requests that will be sent is determined by the `race_count` field."
- HTTPRequestDoc.Fields[24].Comments[encoder.LineComment] = "Race determines if all the request have to be attempted at the same time (Race Condition)"
- HTTPRequestDoc.Fields[25].Name = "req-condition"
+ HTTPRequestDoc.Fields[24].Description = "Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests.\n\nThis uses the [rawhttp](https://github.com/projectdiscovery/rawhttp) engine to achieve complete\ncontrol over the request, with no normalization performed by the client."
+ HTTPRequestDoc.Fields[24].Comments[encoder.LineComment] = "Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests."
+ HTTPRequestDoc.Fields[25].Name = "race"
HTTPRequestDoc.Fields[25].Type = "bool"
HTTPRequestDoc.Fields[25].Note = ""
- HTTPRequestDoc.Fields[25].Description = "ReqCondition automatically assigns numbers to requests and preserves their history.\n\nThis allows matching on them later for multi-request conditions."
- HTTPRequestDoc.Fields[25].Comments[encoder.LineComment] = "ReqCondition automatically assigns numbers to requests and preserves their history."
- HTTPRequestDoc.Fields[26].Name = "stop-at-first-match"
+ HTTPRequestDoc.Fields[25].Description = "Race determines if all the request have to be attempted at the same time (Race Condition)\n\nThe actual number of requests that will be sent is determined by the `race_count` field."
+ HTTPRequestDoc.Fields[25].Comments[encoder.LineComment] = "Race determines if all the request have to be attempted at the same time (Race Condition)"
+ HTTPRequestDoc.Fields[26].Name = "req-condition"
HTTPRequestDoc.Fields[26].Type = "bool"
HTTPRequestDoc.Fields[26].Note = ""
- HTTPRequestDoc.Fields[26].Description = "StopAtFirstMatch stops the execution of the requests and template as soon as a match is found."
- HTTPRequestDoc.Fields[26].Comments[encoder.LineComment] = "StopAtFirstMatch stops the execution of the requests and template as soon as a match is found."
- HTTPRequestDoc.Fields[27].Name = "skip-variables-check"
+ HTTPRequestDoc.Fields[26].Description = "ReqCondition automatically assigns numbers to requests and preserves their history.\n\nThis allows matching on them later for multi-request conditions."
+ HTTPRequestDoc.Fields[26].Comments[encoder.LineComment] = "ReqCondition automatically assigns numbers to requests and preserves their history."
+ HTTPRequestDoc.Fields[27].Name = "stop-at-first-match"
HTTPRequestDoc.Fields[27].Type = "bool"
HTTPRequestDoc.Fields[27].Note = ""
- HTTPRequestDoc.Fields[27].Description = "SkipVariablesCheck skips the check for unresolved variables in request"
- HTTPRequestDoc.Fields[27].Comments[encoder.LineComment] = "SkipVariablesCheck skips the check for unresolved variables in request"
- HTTPRequestDoc.Fields[28].Name = "iterate-all"
+ HTTPRequestDoc.Fields[27].Description = "StopAtFirstMatch stops the execution of the requests and template as soon as a match is found."
+ HTTPRequestDoc.Fields[27].Comments[encoder.LineComment] = "StopAtFirstMatch stops the execution of the requests and template as soon as a match is found."
+ HTTPRequestDoc.Fields[28].Name = "skip-variables-check"
HTTPRequestDoc.Fields[28].Type = "bool"
HTTPRequestDoc.Fields[28].Note = ""
- HTTPRequestDoc.Fields[28].Description = "IterateAll iterates all the values extracted from internal extractors"
- HTTPRequestDoc.Fields[28].Comments[encoder.LineComment] = "IterateAll iterates all the values extracted from internal extractors"
- HTTPRequestDoc.Fields[29].Name = "digest-username"
- HTTPRequestDoc.Fields[29].Type = "string"
+ HTTPRequestDoc.Fields[28].Description = "SkipVariablesCheck skips the check for unresolved variables in request"
+ HTTPRequestDoc.Fields[28].Comments[encoder.LineComment] = "SkipVariablesCheck skips the check for unresolved variables in request"
+ HTTPRequestDoc.Fields[29].Name = "iterate-all"
+ HTTPRequestDoc.Fields[29].Type = "bool"
HTTPRequestDoc.Fields[29].Note = ""
- HTTPRequestDoc.Fields[29].Description = "DigestAuthUsername specifies the username for digest authentication"
- HTTPRequestDoc.Fields[29].Comments[encoder.LineComment] = "DigestAuthUsername specifies the username for digest authentication"
- HTTPRequestDoc.Fields[30].Name = "digest-password"
+ HTTPRequestDoc.Fields[29].Description = "IterateAll iterates all the values extracted from internal extractors"
+ HTTPRequestDoc.Fields[29].Comments[encoder.LineComment] = "IterateAll iterates all the values extracted from internal extractors"
+ HTTPRequestDoc.Fields[30].Name = "digest-username"
HTTPRequestDoc.Fields[30].Type = "string"
HTTPRequestDoc.Fields[30].Note = ""
- HTTPRequestDoc.Fields[30].Description = "DigestAuthPassword specifies the password for digest authentication"
- HTTPRequestDoc.Fields[30].Comments[encoder.LineComment] = "DigestAuthPassword specifies the password for digest authentication"
- HTTPRequestDoc.Fields[31].Name = "disable-path-automerge"
- HTTPRequestDoc.Fields[31].Type = "bool"
+ HTTPRequestDoc.Fields[30].Description = "DigestAuthUsername specifies the username for digest authentication"
+ HTTPRequestDoc.Fields[30].Comments[encoder.LineComment] = "DigestAuthUsername specifies the username for digest authentication"
+ HTTPRequestDoc.Fields[31].Name = "digest-password"
+ HTTPRequestDoc.Fields[31].Type = "string"
HTTPRequestDoc.Fields[31].Note = ""
- HTTPRequestDoc.Fields[31].Description = "DisablePathAutomerge disables merging target url path with raw request path"
- HTTPRequestDoc.Fields[31].Comments[encoder.LineComment] = "DisablePathAutomerge disables merging target url path with raw request path"
+ HTTPRequestDoc.Fields[31].Description = "DigestAuthPassword specifies the password for digest authentication"
+ HTTPRequestDoc.Fields[31].Comments[encoder.LineComment] = "DigestAuthPassword specifies the password for digest authentication"
+ HTTPRequestDoc.Fields[32].Name = "disable-path-automerge"
+ HTTPRequestDoc.Fields[32].Type = "bool"
+ HTTPRequestDoc.Fields[32].Note = ""
+ HTTPRequestDoc.Fields[32].Description = "DisablePathAutomerge disables merging target url path with raw request path"
+ HTTPRequestDoc.Fields[32].Comments[encoder.LineComment] = "DisablePathAutomerge disables merging target url path with raw request path"
+ HTTPRequestDoc.Fields[33].Name = "filters"
+ HTTPRequestDoc.Fields[33].Type = "[]matchers.Matcher"
+ HTTPRequestDoc.Fields[33].Note = ""
+ HTTPRequestDoc.Fields[33].Description = "Filter is matcher-like field to check if fuzzing should be performed on this request or not"
+ HTTPRequestDoc.Fields[33].Comments[encoder.LineComment] = "Filter is matcher-like field to check if fuzzing should be performed on this request or not"
+ HTTPRequestDoc.Fields[34].Name = "filters-condition"
+ HTTPRequestDoc.Fields[34].Type = "string"
+ HTTPRequestDoc.Fields[34].Note = ""
+ HTTPRequestDoc.Fields[34].Description = "Filter condition is the condition to apply on the filter (AND/OR). Default is OR"
+ HTTPRequestDoc.Fields[34].Comments[encoder.LineComment] = "Filter condition is the condition to apply on the filter (AND/OR). Default is OR"
GENERATORSAttackTypeHolderDoc.Type = "generators.AttackTypeHolder"
GENERATORSAttackTypeHolderDoc.Comments[encoder.LineComment] = " AttackTypeHolder is used to hold internal type of the protocol"
@@ -729,7 +747,7 @@ func init() {
FieldName: "fuzzing",
},
}
- FUZZRuleDoc.Fields = make([]encoder.Doc, 7)
+ FUZZRuleDoc.Fields = make([]encoder.Doc, 8)
FUZZRuleDoc.Fields[0].Name = "type"
FUZZRuleDoc.Fields[0].Type = "string"
FUZZRuleDoc.Fields[0].Note = ""
@@ -780,12 +798,26 @@ func init() {
FUZZRuleDoc.Fields[5].AddExample("Examples of value regex", []string{"https?://.*"})
FUZZRuleDoc.Fields[6].Name = "fuzz"
- FUZZRuleDoc.Fields[6].Type = "[]string"
+ FUZZRuleDoc.Fields[6].Type = "SliceOrMapSlice"
FUZZRuleDoc.Fields[6].Note = ""
- FUZZRuleDoc.Fields[6].Description = "Fuzz is the list of payloads to perform substitutions with."
- FUZZRuleDoc.Fields[6].Comments[encoder.LineComment] = "Fuzz is the list of payloads to perform substitutions with."
-
- FUZZRuleDoc.Fields[6].AddExample("Examples of fuzz", []string{"{{ssrf}}", "{{interactsh-url}}", "example-value"})
+ FUZZRuleDoc.Fields[6].Description = "description: |\n Fuzz is the list of payloads to perform substitutions with.\n examples:\n - name: Examples of fuzz\n value: >\n []string{\"{{ssrf}}\", \"{{interactsh-url}}\", \"example-value\"}\n or\n x-header: 1\n x-header: 2"
+ FUZZRuleDoc.Fields[6].Comments[encoder.LineComment] = " description: |"
+ FUZZRuleDoc.Fields[7].Name = "replace-regex"
+ FUZZRuleDoc.Fields[7].Type = "string"
+ FUZZRuleDoc.Fields[7].Note = ""
+ FUZZRuleDoc.Fields[7].Description = "replace-regex is regex for regex-replace rule type\nit is only required for replace-regex rule type"
+ FUZZRuleDoc.Fields[7].Comments[encoder.LineComment] = "replace-regex is regex for regex-replace rule type"
+
+ SliceOrMapSliceDoc.Type = "SliceOrMapSlice"
+ SliceOrMapSliceDoc.Comments[encoder.LineComment] = ""
+ SliceOrMapSliceDoc.Description = ""
+ SliceOrMapSliceDoc.AppearsIn = []encoder.Appearance{
+ {
+ TypeName: "fuzz.Rule",
+ FieldName: "fuzz",
+ },
+ }
+ SliceOrMapSliceDoc.Fields = make([]encoder.Doc, 0)
SignatureTypeHolderDoc.Type = "SignatureTypeHolder"
SignatureTypeHolderDoc.Comments[encoder.LineComment] = " SignatureTypeHolder is used to hold internal type of the signature"
@@ -798,6 +830,167 @@ func init() {
}
SignatureTypeHolderDoc.Fields = make([]encoder.Doc, 0)
+ MATCHERSMatcherDoc.Type = "matchers.Matcher"
+ MATCHERSMatcherDoc.Comments[encoder.LineComment] = " Matcher is used to match a part in the output from a protocol."
+ MATCHERSMatcherDoc.Description = "Matcher is used to match a part in the output from a protocol."
+ MATCHERSMatcherDoc.AppearsIn = []encoder.Appearance{
+ {
+ TypeName: "http.Request",
+ FieldName: "filters",
+ },
+ }
+ MATCHERSMatcherDoc.Fields = make([]encoder.Doc, 16)
+ MATCHERSMatcherDoc.Fields[0].Name = "type"
+ MATCHERSMatcherDoc.Fields[0].Type = "MatcherTypeHolder"
+ MATCHERSMatcherDoc.Fields[0].Note = ""
+ MATCHERSMatcherDoc.Fields[0].Description = "Type is the type of the matcher."
+ MATCHERSMatcherDoc.Fields[0].Comments[encoder.LineComment] = "Type is the type of the matcher."
+ MATCHERSMatcherDoc.Fields[1].Name = "condition"
+ MATCHERSMatcherDoc.Fields[1].Type = "string"
+ MATCHERSMatcherDoc.Fields[1].Note = ""
+ MATCHERSMatcherDoc.Fields[1].Description = "Condition is the optional condition between two matcher variables. By default,\nthe condition is assumed to be OR."
+ MATCHERSMatcherDoc.Fields[1].Comments[encoder.LineComment] = "Condition is the optional condition between two matcher variables. By default,"
+ MATCHERSMatcherDoc.Fields[1].Values = []string{
+ "and",
+ "or",
+ }
+ MATCHERSMatcherDoc.Fields[2].Name = "part"
+ MATCHERSMatcherDoc.Fields[2].Type = "string"
+ MATCHERSMatcherDoc.Fields[2].Note = ""
+ MATCHERSMatcherDoc.Fields[2].Description = "Part is the part of the request response to match data from.\n\nEach protocol exposes a lot of different parts which are well\ndocumented in docs for each request type."
+ MATCHERSMatcherDoc.Fields[2].Comments[encoder.LineComment] = "Part is the part of the request response to match data from."
+
+ MATCHERSMatcherDoc.Fields[2].AddExample("", "body")
+
+ MATCHERSMatcherDoc.Fields[2].AddExample("", "raw")
+ MATCHERSMatcherDoc.Fields[3].Name = "negative"
+ MATCHERSMatcherDoc.Fields[3].Type = "bool"
+ MATCHERSMatcherDoc.Fields[3].Note = ""
+ MATCHERSMatcherDoc.Fields[3].Description = "Negative specifies if the match should be reversed\nIt will only match if the condition is not true."
+ MATCHERSMatcherDoc.Fields[3].Comments[encoder.LineComment] = "Negative specifies if the match should be reversed"
+ MATCHERSMatcherDoc.Fields[4].Name = "name"
+ MATCHERSMatcherDoc.Fields[4].Type = "string"
+ MATCHERSMatcherDoc.Fields[4].Note = ""
+ MATCHERSMatcherDoc.Fields[4].Description = "Name of the matcher. Name should be lowercase and must not contain\nspaces or underscores (_)."
+ MATCHERSMatcherDoc.Fields[4].Comments[encoder.LineComment] = "Name of the matcher. Name should be lowercase and must not contain"
+
+ MATCHERSMatcherDoc.Fields[4].AddExample("", "cookie-matcher")
+ MATCHERSMatcherDoc.Fields[5].Name = "status"
+ MATCHERSMatcherDoc.Fields[5].Type = "[]int"
+ MATCHERSMatcherDoc.Fields[5].Note = ""
+ MATCHERSMatcherDoc.Fields[5].Description = "Status are the acceptable status codes for the response."
+ MATCHERSMatcherDoc.Fields[5].Comments[encoder.LineComment] = "Status are the acceptable status codes for the response."
+
+ MATCHERSMatcherDoc.Fields[5].AddExample("", []int{200, 302})
+ MATCHERSMatcherDoc.Fields[6].Name = "size"
+ MATCHERSMatcherDoc.Fields[6].Type = "[]int"
+ MATCHERSMatcherDoc.Fields[6].Note = ""
+ MATCHERSMatcherDoc.Fields[6].Description = "Size is the acceptable size for the response"
+ MATCHERSMatcherDoc.Fields[6].Comments[encoder.LineComment] = "Size is the acceptable size for the response"
+
+ MATCHERSMatcherDoc.Fields[6].AddExample("", []int{3029, 2042})
+ MATCHERSMatcherDoc.Fields[7].Name = "words"
+ MATCHERSMatcherDoc.Fields[7].Type = "[]string"
+ MATCHERSMatcherDoc.Fields[7].Note = ""
+ MATCHERSMatcherDoc.Fields[7].Description = "Words contains word patterns required to be present in the response part."
+ MATCHERSMatcherDoc.Fields[7].Comments[encoder.LineComment] = "Words contains word patterns required to be present in the response part."
+
+ MATCHERSMatcherDoc.Fields[7].AddExample("Match for Outlook mail protection domain", []string{"mail.protection.outlook.com"})
+
+ MATCHERSMatcherDoc.Fields[7].AddExample("Match for application/json in response headers", []string{"application/json"})
+ MATCHERSMatcherDoc.Fields[8].Name = "regex"
+ MATCHERSMatcherDoc.Fields[8].Type = "[]string"
+ MATCHERSMatcherDoc.Fields[8].Note = ""
+ MATCHERSMatcherDoc.Fields[8].Description = "Regex contains Regular Expression patterns required to be present in the response part."
+ MATCHERSMatcherDoc.Fields[8].Comments[encoder.LineComment] = "Regex contains Regular Expression patterns required to be present in the response part."
+
+ MATCHERSMatcherDoc.Fields[8].AddExample("Match for Linkerd Service via Regex", []string{`(?mi)^Via\\s*?:.*?linkerd.*$`})
+
+ MATCHERSMatcherDoc.Fields[8].AddExample("Match for Open Redirect via Location header", []string{`(?m)^(?:Location\\s*?:\\s*?)(?:https?://|//)?(?:[a-zA-Z0-9\\-_\\.@]*)example\\.com.*$`})
+ MATCHERSMatcherDoc.Fields[9].Name = "binary"
+ MATCHERSMatcherDoc.Fields[9].Type = "[]string"
+ MATCHERSMatcherDoc.Fields[9].Note = ""
+ MATCHERSMatcherDoc.Fields[9].Description = "Binary are the binary patterns required to be present in the response part."
+ MATCHERSMatcherDoc.Fields[9].Comments[encoder.LineComment] = "Binary are the binary patterns required to be present in the response part."
+
+ MATCHERSMatcherDoc.Fields[9].AddExample("Match for Springboot Heapdump Actuator \"JAVA PROFILE\", \"HPROF\", \"Gunzip magic byte\"", []string{"4a4156412050524f46494c45", "4850524f46", "1f8b080000000000"})
+
+ MATCHERSMatcherDoc.Fields[9].AddExample("Match for 7zip files", []string{"377ABCAF271C"})
+ MATCHERSMatcherDoc.Fields[10].Name = "dsl"
+ MATCHERSMatcherDoc.Fields[10].Type = "[]string"
+ MATCHERSMatcherDoc.Fields[10].Note = ""
+ MATCHERSMatcherDoc.Fields[10].Description = "DSL are the dsl expressions that will be evaluated as part of nuclei matching rules.\nA list of these helper functions are available [here](https://nuclei.projectdiscovery.io/templating-guide/helper-functions/)."
+ MATCHERSMatcherDoc.Fields[10].Comments[encoder.LineComment] = "DSL are the dsl expressions that will be evaluated as part of nuclei matching rules."
+
+ MATCHERSMatcherDoc.Fields[10].AddExample("DSL Matcher for package.json file", []string{"contains(body, 'packages') && contains(tolower(all_headers), 'application/octet-stream') && status_code == 200"})
+
+ MATCHERSMatcherDoc.Fields[10].AddExample("DSL Matcher for missing strict transport security header", []string{"!contains(tolower(all_headers), ''strict-transport-security'')"})
+ MATCHERSMatcherDoc.Fields[11].Name = "xpath"
+ MATCHERSMatcherDoc.Fields[11].Type = "[]string"
+ MATCHERSMatcherDoc.Fields[11].Note = ""
+ MATCHERSMatcherDoc.Fields[11].Description = "XPath are the xpath queries expressions that will be evaluated against the response part."
+ MATCHERSMatcherDoc.Fields[11].Comments[encoder.LineComment] = "XPath are the xpath queries expressions that will be evaluated against the response part."
+
+ MATCHERSMatcherDoc.Fields[11].AddExample("XPath Matcher to check a title", []string{"/html/head/title[contains(text(), 'How to Find XPath')]"})
+
+ MATCHERSMatcherDoc.Fields[11].AddExample("XPath Matcher for finding links with target=\"_blank\"", []string{"//a[@target=\"_blank\"]"})
+ MATCHERSMatcherDoc.Fields[12].Name = "encoding"
+ MATCHERSMatcherDoc.Fields[12].Type = "string"
+ MATCHERSMatcherDoc.Fields[12].Note = ""
+ MATCHERSMatcherDoc.Fields[12].Description = "Encoding specifies the encoding for the words field if any."
+ MATCHERSMatcherDoc.Fields[12].Comments[encoder.LineComment] = "Encoding specifies the encoding for the words field if any."
+ MATCHERSMatcherDoc.Fields[12].Values = []string{
+ "hex",
+ }
+ MATCHERSMatcherDoc.Fields[13].Name = "case-insensitive"
+ MATCHERSMatcherDoc.Fields[13].Type = "bool"
+ MATCHERSMatcherDoc.Fields[13].Note = ""
+ MATCHERSMatcherDoc.Fields[13].Description = "CaseInsensitive enables case-insensitive matches. Default is false."
+ MATCHERSMatcherDoc.Fields[13].Comments[encoder.LineComment] = "CaseInsensitive enables case-insensitive matches. Default is false."
+ MATCHERSMatcherDoc.Fields[13].Values = []string{
+ "false",
+ "true",
+ }
+ MATCHERSMatcherDoc.Fields[14].Name = "match-all"
+ MATCHERSMatcherDoc.Fields[14].Type = "bool"
+ MATCHERSMatcherDoc.Fields[14].Note = ""
+ MATCHERSMatcherDoc.Fields[14].Description = "MatchAll enables matching for all matcher values. Default is false."
+ MATCHERSMatcherDoc.Fields[14].Comments[encoder.LineComment] = "MatchAll enables matching for all matcher values. Default is false."
+ MATCHERSMatcherDoc.Fields[14].Values = []string{
+ "false",
+ "true",
+ }
+ MATCHERSMatcherDoc.Fields[15].Name = "internal"
+ MATCHERSMatcherDoc.Fields[15].Type = "bool"
+ MATCHERSMatcherDoc.Fields[15].Note = ""
+ MATCHERSMatcherDoc.Fields[15].Description = "description: |\n Internal when true hides the matcher from output. Default is false.\n It is meant to be used in multiprotocol / flow templates to create internal matcher condition without printing it in output.\n or other similar use cases.\n values:\n - false\n - true"
+ MATCHERSMatcherDoc.Fields[15].Comments[encoder.LineComment] = " description: |"
+
+ MatcherTypeHolderDoc.Type = "MatcherTypeHolder"
+ MatcherTypeHolderDoc.Comments[encoder.LineComment] = " MatcherTypeHolder is used to hold internal type of the matcher"
+ MatcherTypeHolderDoc.Description = "MatcherTypeHolder is used to hold internal type of the matcher"
+ MatcherTypeHolderDoc.AppearsIn = []encoder.Appearance{
+ {
+ TypeName: "matchers.Matcher",
+ FieldName: "type",
+ },
+ }
+ MatcherTypeHolderDoc.Fields = make([]encoder.Doc, 1)
+ MatcherTypeHolderDoc.Fields[0].Name = ""
+ MatcherTypeHolderDoc.Fields[0].Type = "MatcherType"
+ MatcherTypeHolderDoc.Fields[0].Note = ""
+ MatcherTypeHolderDoc.Fields[0].Description = ""
+ MatcherTypeHolderDoc.Fields[0].Comments[encoder.LineComment] = ""
+ MatcherTypeHolderDoc.Fields[0].EnumFields = []string{
+ "word",
+ "regex",
+ "binary",
+ "status",
+ "size",
+ "dsl",
+ "xpath",
+ }
+
DNSRequestDoc.Type = "dns.Request"
DNSRequestDoc.Comments[encoder.LineComment] = " Request contains a DNS protocol request to be made from a template"
DNSRequestDoc.Description = "Request contains a DNS protocol request to be made from a template"
@@ -1805,7 +1998,10 @@ func GetTemplateDoc() *encoder.FileDoc {
&GENERATORSAttackTypeHolderDoc,
&HTTPMethodTypeHolderDoc,
&FUZZRuleDoc,
+ &SliceOrMapSliceDoc,
&SignatureTypeHolderDoc,
+ &MATCHERSMatcherDoc,
+ &MatcherTypeHolderDoc,
&DNSRequestDoc,
&DNSRequestTypeHolderDoc,
&FILERequestDoc,
diff --git a/pkg/templates/workflows.go b/pkg/templates/workflows.go
index 94e2c11c9b..03c84e2e5d 100644
--- a/pkg/templates/workflows.go
+++ b/pkg/templates/workflows.go
@@ -6,9 +6,15 @@ import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/model"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
+ "github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
)
+const (
+ // Note: we redefine to avoid cyclic dependency but it should be same as parsers.SkippedUnsignedStats
+ SkippedUnsignedStats = "skipped-unsigned-stats" // tracks loading of unsigned templates
+)
+
// compileWorkflow compiles the workflow for execution
func compileWorkflow(path string, preprocessor Preprocessor, options *protocols.ExecutorOptions, workflow *workflows.Workflow, loader model.WorkflowLoader) {
for _, workflow := range workflow.Workflows {
@@ -69,7 +75,6 @@ func parseWorkflowTemplate(workflow *workflows.WorkflowTemplate, preprocessor Pr
}
var workflowTemplates []*Template
-
for _, path := range paths {
template, err := Parse(path, preprocessor, options.Copy())
if err != nil {
@@ -80,6 +85,23 @@ func parseWorkflowTemplate(workflow *workflows.WorkflowTemplate, preprocessor Pr
gologger.Warning().Msgf("Could not parse workflow template %s: no executer found\n", path)
continue
}
+
+ if options.Options.DisableUnsignedTemplates && !template.Verified {
+ // skip unverified templates when prompted to do so
+ stats.Increment(SkippedUnsignedStats)
+ continue
+ }
+
+ if len(template.RequestsCode) > 0 {
+ if !options.Options.EnableCodeTemplates {
+ gologger.Warning().Msgf("`-code` flag not found, skipping code template from workflow: %v\n", path)
+ continue
+ } else if !template.Verified {
+ // unverfied code templates are not allowed in workflows
+ gologger.Warning().Msgf("skipping unverified code template from workflow: %v\n", path)
+ continue
+ }
+ }
workflowTemplates = append(workflowTemplates, template)
}
diff --git a/pkg/testutils/fuzzplayground/db.go b/pkg/testutils/fuzzplayground/db.go
new file mode 100644
index 0000000000..8344f5dc4f
--- /dev/null
+++ b/pkg/testutils/fuzzplayground/db.go
@@ -0,0 +1,150 @@
+package fuzzplayground
+
+import (
+ "database/sql"
+ "encoding/xml"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+
+ _ "github.com/mattn/go-sqlite3"
+)
+
+var (
+ db *sql.DB
+ tempDBDir string
+)
+
+func init() {
+ dir, err := os.MkdirTemp("", "fuzzplayground-*")
+ if err != nil {
+ panic(err)
+ }
+ tempDBDir = dir
+
+ db, err = sql.Open("sqlite3", fmt.Sprintf("file:%v/test.db?cache=shared&mode=memory", tempDBDir))
+ if err != nil {
+ panic(err)
+ }
+ addDummyUsers(db)
+ addDummyPosts(db)
+}
+
+// Cleanup cleans up the temporary database directory
+func Cleanup() {
+ if db != nil {
+ _ = db.Close()
+ }
+ if tempDBDir != "" {
+ _ = os.RemoveAll(tempDBDir)
+ }
+}
+
+type User struct {
+ XMLName xml.Name `xml:"user"`
+ ID int `xml:"id"`
+ Name string `xml:"name"`
+ Age int `xml:"age"`
+ Role string `xml:"role"`
+}
+
+func addDummyUsers(db *sql.DB) {
+ _, err := db.Exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER, role TEXT)")
+ if err != nil {
+ panic(err)
+ }
+ _, err = db.Exec("INSERT INTO users (id , name, age, role) VALUES (1,'admin', 30, 'admin')")
+ if err != nil {
+ panic(err)
+ }
+ _, err = db.Exec("INSERT INTO users (id , name, age, role) VALUES (75,'user', 30, 'user')")
+ if err != nil {
+ panic(err)
+ }
+}
+
+func patchUnsanitizedUser(db *sql.DB, user User) error {
+ setClauses := ""
+
+ if user.Name != "" {
+ setClauses += "name = '" + user.Name + "', "
+ }
+ if user.Age > 0 {
+ setClauses += "age = " + strconv.Itoa(user.Age) + ", "
+ }
+ if user.Role != "" {
+ setClauses += "role = '" + user.Role + "', "
+ }
+ if setClauses == "" {
+ // No fields to update
+ return nil
+ }
+ setClauses = strings.TrimSuffix(setClauses, ", ")
+
+ query := "UPDATE users SET " + setClauses + " WHERE id = ?"
+ _, err := db.Exec(query, user.ID)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func getUnsanitizedUser(db *sql.DB, id string) (User, error) {
+ var user User
+ err := db.QueryRow("SELECT id, name, age, role FROM users WHERE id = "+id).Scan(&user.ID, &user.Name, &user.Age, &user.Role)
+ if err != nil {
+ return user, err
+ }
+ return user, nil
+}
+
+type Posts struct {
+ ID int
+ Title string
+ Content string
+ Lang string
+}
+
+func addDummyPosts(db *sql.DB) {
+ _, err := db.Exec("CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY, title TEXT, content TEXT, lang TEXT)")
+ if err != nil {
+ panic(err)
+ }
+ // Inserting English dummy posts
+ _, err = db.Exec("INSERT INTO posts (id, title, content, lang) VALUES (1, 'The Joy of Programming', 'Programming is like painting a canvas with logic.', 'en')")
+ if err != nil {
+ panic(err)
+ }
+ _, err = db.Exec("INSERT INTO posts (id, title, content, lang) VALUES (2, 'A Journey Through Code', 'Every line of code tells a story.', 'en')")
+ if err != nil {
+ panic(err)
+ }
+ // Inserting a Spanish dummy post
+ _, err = db.Exec("INSERT INTO posts (id, title, content, lang) VALUES (3, 'La belleza del código', 'Cada función es un poema en un mar de algoritmos.', 'es')")
+ if err != nil {
+ panic(err)
+ }
+}
+
+func getUnsanitizedPostsByLang(db *sql.DB, lang string) ([]Posts, error) {
+ var posts []Posts
+ query := "SELECT id, title, content, lang FROM posts WHERE lang = '" + lang + "'"
+ rows, err := db.Query(query)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var post Posts
+ if err := rows.Scan(&post.ID, &post.Title, &post.Content, &post.Lang); err != nil {
+ return nil, err
+ }
+ posts = append(posts, post)
+ }
+ if err = rows.Err(); err != nil {
+ return nil, err
+ }
+ return posts, nil
+}
diff --git a/pkg/testutils/fuzzplayground/server.go b/pkg/testutils/fuzzplayground/server.go
new file mode 100644
index 0000000000..04f00a99df
--- /dev/null
+++ b/pkg/testutils/fuzzplayground/server.go
@@ -0,0 +1,212 @@
+// This package provides a mock server for testing fuzzing templates
+package fuzzplayground
+
+import (
+ "encoding/xml"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os/exec"
+ "strconv"
+ "strings"
+
+ "github.com/labstack/echo/v4"
+ "github.com/labstack/echo/v4/middleware"
+ "github.com/projectdiscovery/retryablehttp-go"
+)
+
+func GetPlaygroundServer() *echo.Echo {
+ e := echo.New()
+ e.Use(middleware.Recover())
+ e.Use(middleware.Logger())
+
+ e.GET("/", indexHandler)
+ e.GET("/info", infoHandler)
+ e.GET("/redirect", redirectHandler)
+ e.GET("/request", requestHandler)
+ e.GET("/email", emailHandler)
+ e.GET("/permissions", permissionsHandler)
+ e.GET("/blog/post", numIdorHandler) // for num based idors like ?id=44
+ e.POST("/reset-password", resetPasswordHandler)
+ e.GET("/host-header-lab", hostHeaderLabHandler)
+ e.GET("/user/:id/profile", userProfileHandler)
+ e.POST("/user", patchUnsanitizedUserHandler)
+ e.GET("/blog/posts", getPostsHandler)
+ return e
+}
+
+var bodyTemplate = `
+
+Fuzz Playground
+
+
+%s
+
+`
+
+func indexHandler(ctx echo.Context) error {
+ return ctx.HTML(200, fmt.Sprintf(bodyTemplate, `Fuzzing Playground
+
+`))
+}
+
+func infoHandler(ctx echo.Context) error {
+ return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Name of user: %s%s%s", ctx.QueryParam("name"), ctx.QueryParam("another"), ctx.QueryParam("random"))))
+}
+
+func redirectHandler(ctx echo.Context) error {
+ url := ctx.QueryParam("redirect_url")
+ return ctx.Redirect(302, url)
+}
+
+func requestHandler(ctx echo.Context) error {
+ url := ctx.QueryParam("url")
+ data, err := retryablehttp.DefaultClient().Get(url)
+ if err != nil {
+ return ctx.HTML(500, err.Error())
+ }
+ defer data.Body.Close()
+
+ body, _ := io.ReadAll(data.Body)
+ return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(body)))
+}
+
+func emailHandler(ctx echo.Context) error {
+ text := ctx.QueryParam("text")
+ if strings.Contains(text, "{{") {
+ trimmed := strings.SplitN(strings.Trim(text[strings.Index(text, "{"):], "{}"), "*", 2)
+ if len(trimmed) < 2 {
+ return ctx.HTML(500, "invalid template")
+ }
+ first, _ := strconv.Atoi(trimmed[0])
+ second, _ := strconv.Atoi(trimmed[1])
+ text = strconv.Itoa(first * second)
+ }
+ return ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf("Text: %s", text)))
+}
+
+func permissionsHandler(ctx echo.Context) error {
+ command := ctx.QueryParam("cmd")
+ fields := strings.Fields(command)
+ cmd := exec.Command(fields[0], fields[1:]...)
+ data, _ := cmd.CombinedOutput()
+
+ return ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(data)))
+}
+
+func numIdorHandler(ctx echo.Context) error {
+ // validate if any numerical query param is present
+ // if not, return 400 if so, return 200
+ for k := range ctx.QueryParams() {
+ if _, err := strconv.Atoi(ctx.QueryParam(k)); err == nil {
+ return ctx.JSON(200, "Profile Info for user with id "+ctx.QueryParam(k))
+ }
+ }
+ return ctx.JSON(400, "No numerical query param found")
+}
+
+func patchUnsanitizedUserHandler(ctx echo.Context) error {
+ var user User
+
+ contentType := ctx.Request().Header.Get("Content-Type")
+ // manually handle unmarshalling data
+ if strings.Contains(contentType, "application/json") {
+ err := ctx.Bind(&user)
+ if err != nil {
+ return ctx.JSON(500, "Invalid JSON data")
+ }
+ } else if strings.Contains(contentType, "application/x-www-form-urlencoded") {
+ user.Name = ctx.FormValue("name")
+ user.Age, _ = strconv.Atoi(ctx.FormValue("age"))
+ user.Role = ctx.FormValue("role")
+ user.ID, _ = strconv.Atoi(ctx.FormValue("id"))
+ } else if strings.Contains(contentType, "application/xml") {
+ bin, _ := io.ReadAll(ctx.Request().Body)
+ err := xml.Unmarshal(bin, &user)
+ if err != nil {
+ return ctx.JSON(500, "Invalid XML data")
+ }
+ } else if strings.Contains(contentType, "multipart/form-data") {
+ user.Name = ctx.FormValue("name")
+ user.Age, _ = strconv.Atoi(ctx.FormValue("age"))
+ user.Role = ctx.FormValue("role")
+ user.ID, _ = strconv.Atoi(ctx.FormValue("id"))
+ } else {
+ return ctx.JSON(500, "Invalid Content-Type")
+ }
+
+ err := patchUnsanitizedUser(db, user)
+ if err != nil {
+ return ctx.JSON(500, err.Error())
+ }
+ return ctx.JSON(200, "User updated successfully")
+}
+
+// resetPassword mock
+func resetPasswordHandler(c echo.Context) error {
+ var m map[string]interface{}
+ if err := c.Bind(&m); err != nil {
+ return c.JSON(500, "Something went wrong")
+ }
+
+ host := c.Request().Header.Get("X-Forwarded-For")
+ if host == "" {
+ return c.JSON(500, "Something went wrong")
+ }
+ resp, err := http.Get("http://internal." + host + "/update?user=1337&pass=" + m["password"].(string))
+ if err != nil {
+ return c.JSON(500, "Something went wrong")
+ }
+ defer resp.Body.Close()
+ return c.JSON(200, "Password reset successfully")
+}
+
+func hostHeaderLabHandler(c echo.Context) error {
+ // vulnerable app has custom routing and trusts x-forwarded-host
+ // to route to internal services
+ if c.Request().Header.Get("X-Forwarded-Host") != "" {
+ resp, err := http.Get("http://" + c.Request().Header.Get("X-Forwarded-Host"))
+ if err != nil {
+ return c.JSON(500, "Something went wrong")
+ }
+ defer resp.Body.Close()
+ c.Response().Header().Set("Content-Type", resp.Header.Get("Content-Type"))
+ c.Response().WriteHeader(resp.StatusCode)
+ _, err = io.Copy(c.Response().Writer, resp.Body)
+ if err != nil {
+ return c.JSON(500, "Something went wrong")
+ }
+ }
+ return c.JSON(200, "Not a Teapot")
+}
+
+func userProfileHandler(ctx echo.Context) error {
+ val, _ := url.PathUnescape(ctx.Param("id"))
+ fmt.Printf("Unescaped: %s\n", val)
+ user, err := getUnsanitizedUser(db, val)
+ if err != nil {
+ return ctx.JSON(500, err.Error())
+ }
+ return ctx.JSON(200, user)
+}
+
+func getPostsHandler(c echo.Context) error {
+ lang, err := c.Cookie("lang")
+ if err != nil {
+ // If the language cookie is missing, default to English
+ lang = new(http.Cookie)
+ lang.Value = "en"
+ }
+ posts, err := getUnsanitizedPostsByLang(db, lang.Value)
+ if err != nil {
+ return c.JSON(http.StatusInternalServerError, err.Error())
+ }
+ return c.JSON(http.StatusOK, posts)
+}
diff --git a/pkg/tmplexec/exec.go b/pkg/tmplexec/exec.go
index 3faaa836c7..3e0e8fdded 100644
--- a/pkg/tmplexec/exec.go
+++ b/pkg/tmplexec/exec.go
@@ -3,6 +3,7 @@ package tmplexec
import (
"errors"
"fmt"
+ "runtime/debug"
"strings"
"sync/atomic"
@@ -34,16 +35,6 @@ var _ protocols.Executer = &TemplateExecuter{}
// NewTemplateExecuter creates a new request TemplateExecuter for list of requests
func NewTemplateExecuter(requests []protocols.Request, options *protocols.ExecutorOptions) (*TemplateExecuter, error) {
- 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
@@ -55,13 +46,11 @@ func NewTemplateExecuter(requests []protocols.Request, options *protocols.Execut
}
e.program = p
} 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 {
+ // only use generic if there is only 1 protocol with only 1 section
+ if len(requests) == 1 {
e.engine = generic.NewGenericEngine(requests, options, e.results)
+ } else {
+ e.engine = multiproto.NewMultiProtocol(requests, options, e.results)
}
}
return e, nil
@@ -113,7 +102,9 @@ func (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
defer func() {
// try catching unknown panics
if r := recover(); r != nil {
- ctx.LogError(fmt.Errorf("panic: %v", r))
+ stacktrace := debug.Stack()
+ ctx.LogError(fmt.Errorf("panic: %v\n%s", r, stacktrace))
+ gologger.Verbose().Msgf("panic: %v\n%s", r, stacktrace)
}
}()
diff --git a/pkg/tmplexec/multiproto/multi.go b/pkg/tmplexec/multiproto/multi.go
index 021c83534d..d164c03aab 100644
--- a/pkg/tmplexec/multiproto/multi.go
+++ b/pkg/tmplexec/multiproto/multi.go
@@ -46,12 +46,13 @@ func (m *MultiProtocol) Compile() error {
func (m *MultiProtocol) ExecuteWithResults(ctx *scan.ScanContext) error {
// put all readonly args into template context
m.options.GetTemplateCtx(ctx.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
+ if event == nil {
+ return
}
+ // log event and generate result for the event
+ ctx.LogEvent(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 {
@@ -97,12 +98,6 @@ func (m *MultiProtocol) ExecuteWithResults(ctx *scan.ScanContext) error {
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/pkg/protocols/common/executer/executem.go#L150
- // this causes addition of duplicated / unncessary variables with prefix template_id_all_variables
- ctx.LogEvent(finalProtoEvent)
-
return nil
}
diff --git a/pkg/types/types.go b/pkg/types/types.go
index 5397eac3e2..45a8788a8e 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -270,6 +270,8 @@ type Options struct {
DisableRedirects bool
// SNI custom hostname
SNI string
+ // InputFileMode specifies the mode of input file (jsonl, burp, openapi, swagger, etc)
+ InputFileMode string
// DialerTimeout sets the timeout for network requests.
DialerTimeout time.Duration
// DialerKeepAlive sets the keep alive duration for network requests.
@@ -362,12 +364,27 @@ type Options struct {
SignTemplates bool
// EnableCodeTemplates enables code templates
EnableCodeTemplates bool
+ // DisableUnsignedTemplates disables processing of unsigned templates
+ DisableUnsignedTemplates bool
// Disables cloud upload
EnableCloudUpload bool
// ScanID is the scan ID to use for cloud upload
ScanID string
// JsConcurrency is the number of concurrent js routines to run
JsConcurrency int
+ // Fuzz enabled execution of fuzzing templates
+ // Note: when Fuzz is enabled other templates will not be executed
+ FuzzTemplates bool
+ // SecretsFile is file containing secrets for nuclei
+ SecretsFile goflags.StringSlice
+ // PreFetchSecrets pre-fetches the secrets from the auth provider
+ PreFetchSecrets bool
+ // FormatUseRequiredOnly only uses required fields when generating requests
+ FormatUseRequiredOnly bool
+ // SkipFormatValidation is used to skip format validation
+ SkipFormatValidation bool
+ // PayloadConcurrency is the number of concurrent payloads to run per template
+ PayloadConcurrency int
}
// ShouldLoadResume resume file
diff --git a/pkg/utils/http_probe.go b/pkg/utils/http_probe.go
index 1e3db9aba7..19059b2af9 100644
--- a/pkg/utils/http_probe.go
+++ b/pkg/utils/http_probe.go
@@ -5,6 +5,7 @@ import (
"net/http"
"github.com/projectdiscovery/httpx/common/httpx"
+ "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/projectdiscovery/useragent"
)
@@ -32,3 +33,18 @@ func ProbeURL(input string, httpxclient *httpx.HTTPX) string {
}
return ""
}
+
+type inputLivenessChecker struct {
+ client *httpx.HTTPX
+}
+
+// ProbeURL probes the scheme for a URL. first HTTPS is tried
+func (i *inputLivenessChecker) ProbeURL(input string) (string, error) {
+ return ProbeURL(input, i.client), nil
+}
+
+// GetInputLivenessChecker returns a new input liveness checker using provided httpx client
+func GetInputLivenessChecker(client *httpx.HTTPX) types.InputLivenessProbe {
+ x := &inputLivenessChecker{client: client}
+ return x
+}
diff --git a/pkg/utils/stats/stats.go b/pkg/utils/stats/stats.go
index a001d98980..590608d1e2 100644
--- a/pkg/utils/stats/stats.go
+++ b/pkg/utils/stats/stats.go
@@ -1,9 +1,11 @@
package stats
import (
+ "fmt"
"sync"
"sync/atomic"
+ "github.com/logrusorgru/aurora"
"github.com/projectdiscovery/gologger"
)
@@ -44,6 +46,12 @@ func DisplayAsWarning(name string) {
Default.DisplayAsWarning(name)
}
+// ForceDisplayWarning forces the display of a warning
+// regardless of current verbosity level
+func ForceDisplayWarning(name string) {
+ Default.ForceDisplayWarning(name)
+}
+
// GetValue returns the value for a set variable
func GetValue(name string) int64 {
return Default.GetValue(name)
@@ -104,6 +112,23 @@ func (s *Storage) DisplayAsWarning(name string) {
gologger.Warning().Label("WRN").Msgf(data.description, dataValue)
}
+// ForceDisplayWarning forces the display of a warning
+// regardless of current verbosity level
+func (s *Storage) ForceDisplayWarning(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.Print().Msgf("[%v] %v", aurora.BrightYellow("WRN"), fmt.Sprintf(data.description, dataValue))
+}
+
// GetValue returns the value for a set variable
func (s *Storage) GetValue(name string) int64 {
s.mutex.RLock()