diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 57ff74e671..599940d7a1 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: go-version: [1.20.x] - os: [ubuntu-latest, windows-latest, macOS-13] + os: [ubuntu-latest, windows-latest, macOS-latest] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml index c029803f31..79b154af8b 100644 --- a/.github/workflows/functional-test.yml +++ b/.github/workflows/functional-test.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macOS-13] + os: [ubuntu-latest, windows-latest, macOS-latest] steps: - name: Set up Go uses: actions/setup-go@v4 diff --git a/SYNTAX-REFERENCE.md b/SYNTAX-REFERENCE.md index 017e16349b..def307bae2 100755 --- a/SYNTAX-REFERENCE.md +++ b/SYNTAX-REFERENCE.md @@ -2745,6 +2745,19 @@ Fuzzing describes schema to fuzz headless requests
+
+ +cookie-reuse bool + +
+
+ +CookieReuse is an optional setting that enables cookie reuse + +
+ +
+ diff --git a/integration_tests/generic/auth/certificate/assets/client.crt b/integration_tests/generic/auth/certificate/assets/client.crt new file mode 100644 index 0000000000..4bbdee24b2 --- /dev/null +++ b/integration_tests/generic/auth/certificate/assets/client.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfkCFFSLOinkkPWOfwuzMHF0B9EZIIghMA0GCSqGSIb3DQEBCwUAMEUx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjMwNjIxMDA0MzA2WhcNMjMwNzIxMDA0 +MzA2WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAp187MX/sPGlTl8WklyTSh8+kYURy2zWmp2FglMvo058NanCD +rjYJvS2FNM2ZwRZH52qBr6rpqIsa8QDFjnlFJK5y4FNr104ZjDr1YXDfX5ftQeeX +wCavFRDdH/s43kGyNjNeSH78EhqmC3z7LxCJT5k8rXRAyC02uBRqQTrRHfNuVvGI +JZ59jxESzRJt89kWuYymkAj16LHffMvcq6HLwr/KG8IyrJJRj5KcDzYorFonTPe9 +rwBlAbU3LpQ4ZlEP1mQA2PdCg3t85pz3n+57Iw839bWrwbjfijXHY3yjbqfEAaqa +md08nByTg8TZlt9UzjWgj0K1DXDMDDkJKqi39QIDAQABMA0GCSqGSIb3DQEBCwUA +A4IBAQAH0MhznadrMFuY2ZYA69FbsvOygMctv8qZW1HrHS0X13IXeW+8uxfb5+gk +yKFgXNMFueyd5PoN9vyC1t9AOBPnI56gaYm/MbDtwEqGo8C+9fjJasY23J90p54t +G6vxcXwo33HVpWBeRBkVF/SePeCn+MKk0jd/JgJS0T0s1Ih6wkn84/83hDk4M2M2 +/yhc3wuNYdf/WB8QAfJAc2YpIfkMOoxGPTsxvREiZrPUyGiWa507hrHcQU0GV8qC +KcnS7UUCT9TtJvQIKHwW68XjBudWpaILBj1TS8hOGseOJydJqbk8wyMTE6fgc1Ss +KfTrfa0HOHIkAU/TfE22Zqfw4z70 +-----END CERTIFICATE----- diff --git a/integration_tests/generic/auth/certificate/assets/client.key b/integration_tests/generic/auth/certificate/assets/client.key new file mode 100644 index 0000000000..96a88abbc9 --- /dev/null +++ b/integration_tests/generic/auth/certificate/assets/client.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCnXzsxf+w8aVOX +xaSXJNKHz6RhRHLbNaanYWCUy+jTnw1qcIOuNgm9LYU0zZnBFkfnaoGvqumoixrx +AMWOeUUkrnLgU2vXThmMOvVhcN9fl+1B55fAJq8VEN0f+zjeQbI2M15IfvwSGqYL +fPsvEIlPmTytdEDILTa4FGpBOtEd825W8Yglnn2PERLNEm3z2Ra5jKaQCPXosd98 +y9yrocvCv8obwjKsklGPkpwPNiisWidM972vAGUBtTculDhmUQ/WZADY90KDe3zm +nPef7nsjDzf1tavBuN+KNcdjfKNup8QBqpqZ3TycHJODxNmW31TONaCPQrUNcMwM +OQkqqLf1AgMBAAECggEAEZiXdorGYUuJeElVFnbOk2ynEXrKwHURgkNgjgQqBCWS +mYAet/ACchsZCAYdhgk7of62h6tmSUvmlzPHkUT6mfKlLSRYEBir4uxH4+ij8z7b +uLQKZi8q9QIC3VviDKvHep9H6ENBaP3YOxj2p2oLpYysrmesb98hA5VR3m26knVb +sfYwLdsrqQ5Tiuzm1Mdaca2HEYq0iXoZqjQHi31h33rw4HFBKcGX6w6PJOon+i2m +eSSaCAJMYFkoS6NafJYwQDuwIwp+IyrYdI/vaAR6s2ufJ4doIjJC7YuPO5jpLpJc +IBBl5e7pu4/rlwJCDARfBWuXFbjtoMAvmM1MojdNOQKBgQDYCIl97ef19LmeYfwk +RW2xhQTVCWwwBBhSl06iB9SeFkzCCRb+AFjhVhcwe4xIWh9GXWZawZC2XSaoTXte +hxcZRFbnpjcpZ6sYKiP4fB/GXGqYDUWovxu8gmXKrXtfwqJMXklfFE7WblGgK5gG +l7OfbuczaRaIQHoIQYzfmeCwmwKBgQDGVhCSNFGiRaDG7k2VfXElaIyL7m7FlsXf +EptolOeoGv5GvVr3CB5TvUqXN3haLZvUbBKRpWrDbP1n1i+77VICOLtq1qf6SEog +1p2PAccGhXXvrL7LJLUr9Hk831D4fSX9TqVzdxwfMdGFepYoS4vm8fkGVCuaytDa +fniJl8TarwKBgQCpTreCrAsY5bz7dcuIGamIcLmCxKm3T95IDDEiJ4ToiI2LnFga +pOcDYtc1tf4RTiAoo1ZuVjk10vdS+7ZuNO1Tbg216rxchNTAUXZzbcPxT8hydiRb +xbrVGFTybNe+CunrdBGIpH/M6hSqtL+mmwm5L8+eqQNxsSZyhf0D2LMRdQKBgD8J +CXk+MZfOY1v2Tygs1zIZeVnb7M7VrYvJYSUq9jliYuBevDN5HBJnPfazhYe7qSQp +OPmbRkRYNm2zEDa9JWxZVY+OK5MLOKwZKbhSy0uSTTpgf78WqpIOwB2NqDFhrRpF +zaXV/FUZw0qV/HVQFWXQD+JoC/fFb/2RZoPsfX83AoGBAM4VPDUa1I16MuuW+FNZ +cVENZK/qsXFMvm3xutezvMSgmCxVSnXy9GP8QbqkfMrDJd3v/HnwrC4ORTlU7rim +AWSvC6CYO1c2RantleA46T90uWW8kP24TK1yWOrRGKuaQYvfokiiLVExq1nA1iSR +/QPLg6vEoPMOLhB7BQBpsFkq +-----END PRIVATE KEY----- diff --git a/integration_tests/generic/auth/certificate/assets/server.crt b/integration_tests/generic/auth/certificate/assets/server.crt new file mode 100644 index 0000000000..aa818acb4d --- /dev/null +++ b/integration_tests/generic/auth/certificate/assets/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDETCCAfkCFHA1RpGfOY5p/vQmeMQ1oRFqH+CGMA0GCSqGSIb3DQEBCwUAMEUx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjMwNjIxMDA0MjQ2WhcNMjMwNzIxMDA0 +MjQ2WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA3VdrKR5hmZ+vyvg6NB2dOL5vEIQ/9DevivnKWqX5mserYLMj +Wq0knVfogewZnrDe+zVC3kOogBQvYk8Z53kTY9qpJT85dMCuW4xDx0JU+cWHul9a +pzF+bvws4paCWIcsGONyocPAx5g07LbPU9civC80QkQqELo1zYiRU1bX8vRJJqbN +TW2mzl9MN3AnCAYTwq8WhVG/1QR3LPQhPR68/1LWrFefQaEWaXT2s+Xv7K7NDXro +WSba4SgKdFd6fyUVMVr/ioT1KT45TP5jbRrW5JJUTdpkiXaIucrZg39f6F5gTZGA +U7bNROUMkqrJJngN9+Hp+YH1GpkKgu9EKA30EQIDAQABMA0GCSqGSIb3DQEBCwUA +A4IBAQAw91bxiAi7DIVsKL3k4B0I+50ZKq9VMVNE3YCTPygpfuRiGQvlITZ5I8I5 +3Ok2wWltgKx6EnicHIlLg42yRj7j3mdgOLMFMrUCfJmdogwnS+k6veG3G1RHUs9r +ATfX49u/hEX2pe7Rvx2VYVIugwrQESgQ21iaf6uUMsrq6W8eYZ31as1nJKpqIGbu +W1fZMSi0RIUJP+mpVBE82IW+gJRi3uKU4HKPqyrU3dviBFdBxb3lNbh34/vdNkIw +4H2CfBxEvdwLYAhWDerlm4wWCmjkMiHfBHPBhhOICTkR25a7NFy27h/UDHjVC/6m +fGshVSBtxVPJP7kcvZ1scIctvFZZ +-----END CERTIFICATE----- diff --git a/integration_tests/generic/auth/certificate/http-get.yaml b/integration_tests/generic/auth/certificate/http-get.yaml new file mode 100644 index 0000000000..c52c577a13 --- /dev/null +++ b/integration_tests/generic/auth/certificate/http-get.yaml @@ -0,0 +1,15 @@ +id: basic-get-with-cert + +info: + name: Basic GET with Cert + author: pdteam + severity: info + +requests: + - method: GET + path: + - "{{BaseURL}}" + matchers: + - type: word + words: + - "Hello" \ No newline at end of file diff --git a/integration_tests/workflow/headless-1.yaml b/integration_tests/workflow/headless-1.yaml new file mode 100644 index 0000000000..2a5895316b --- /dev/null +++ b/integration_tests/workflow/headless-1.yaml @@ -0,0 +1,16 @@ +id: headless-1 +info: + name: Headless 1 + author: pdteam + severity: info + tags: headless + +headless: + - cookie-reuse: true + steps: + - action: navigate + args: + url: "{{BaseURL}}/headless1" + + - action: waitload + \ No newline at end of file diff --git a/integration_tests/workflow/http-1.yaml b/integration_tests/workflow/http-1.yaml new file mode 100644 index 0000000000..cdebddf3d5 --- /dev/null +++ b/integration_tests/workflow/http-1.yaml @@ -0,0 +1,12 @@ +id: http1 + +info: + name: http1 + author: pdteam + severity: info + +http: + - method: GET + path: + - "{{BaseURL}}/http1" + cookie-reuse: true \ No newline at end of file diff --git a/integration_tests/workflow/http-2.yaml b/integration_tests/workflow/http-2.yaml new file mode 100644 index 0000000000..e5000e0561 --- /dev/null +++ b/integration_tests/workflow/http-2.yaml @@ -0,0 +1,12 @@ +id: http2 + +info: + name: http2 + author: pdteam + severity: info + +http: + - method: GET + path: + - "{{BaseURL}}/http2" + cookie-reuse: true \ No newline at end of file diff --git a/integration_tests/workflow/http-3.yaml b/integration_tests/workflow/http-3.yaml new file mode 100644 index 0000000000..230408dd7b --- /dev/null +++ b/integration_tests/workflow/http-3.yaml @@ -0,0 +1,12 @@ +id: http3 + +info: + name: http3 + author: pdteam + severity: info + +http: + - method: GET + path: + - "{{BaseURL}}/http3" + cookie-reuse: true \ No newline at end of file diff --git a/integration_tests/workflow/http-value-share-template-1.yaml b/integration_tests/workflow/http-value-share-template-1.yaml index a273e113af..c2f6123dfb 100644 --- a/integration_tests/workflow/http-value-share-template-1.yaml +++ b/integration_tests/workflow/http-value-share-template-1.yaml @@ -5,7 +5,7 @@ info: author: pdteam severity: info -requests: +http: - path: - "{{BaseURL}}/path1" extractors: diff --git a/integration_tests/workflow/http-value-share-template-2.yaml b/integration_tests/workflow/http-value-share-template-2.yaml index 7bac99f3db..f391aae1b8 100644 --- a/integration_tests/workflow/http-value-share-template-2.yaml +++ b/integration_tests/workflow/http-value-share-template-2.yaml @@ -5,7 +5,7 @@ info: author: pdteam severity: info -requests: +http: - raw: - | GET /path2 HTTP/1.1 diff --git a/integration_tests/workflow/match-1.yaml b/integration_tests/workflow/match-1.yaml index c7e07e8cf4..854652c98d 100644 --- a/integration_tests/workflow/match-1.yaml +++ b/integration_tests/workflow/match-1.yaml @@ -5,7 +5,7 @@ info: author: pdteam severity: info -requests: +http: - method: GET path: - "{{BaseURL}}" diff --git a/integration_tests/workflow/match-2.yaml b/integration_tests/workflow/match-2.yaml index 01e9d4b6f8..2431ad10ca 100644 --- a/integration_tests/workflow/match-2.yaml +++ b/integration_tests/workflow/match-2.yaml @@ -5,7 +5,7 @@ info: author: pdteam severity: info -requests: +http: - method: GET path: - "{{BaseURL}}" diff --git a/integration_tests/workflow/nomatch-1.yaml b/integration_tests/workflow/nomatch-1.yaml index 71f126421e..ec71d9be80 100644 --- a/integration_tests/workflow/nomatch-1.yaml +++ b/integration_tests/workflow/nomatch-1.yaml @@ -5,7 +5,7 @@ info: author: pdteam severity: info -requests: +http: - method: GET path: - "{{BaseURL}}" diff --git a/integration_tests/workflow/shared-cookie.yaml b/integration_tests/workflow/shared-cookie.yaml new file mode 100644 index 0000000000..f2d68be2cf --- /dev/null +++ b/integration_tests/workflow/shared-cookie.yaml @@ -0,0 +1,15 @@ +id: workflow-shared-cookies + +info: + name: Test Workflow Shared Cookies + author: pdteam + severity: info + +workflows: + # store cookies to standard http client cookie-jar + - template: workflow/http-1.yaml + - template: workflow/http-2.yaml + # store cookie in native browser context + - template: workflow/headless-1.yaml + # retrive 2 standard library cookies + headless cookie + - template: workflow/http-3.yaml \ No newline at end of file diff --git a/nuclei-jsonschema.json b/nuclei-jsonschema.json index 5a4d76573f..452d4fd853 100644 --- a/nuclei-jsonschema.json +++ b/nuclei-jsonschema.json @@ -727,6 +727,11 @@ "type": "array", "title": "fuzzin rules for http fuzzing", "description": "Fuzzing describes rule schema to fuzz headless requests" + }, + "cookie-reuse": { + "type": "boolean", + "title": "optional cookie reuse enable", + "description": "Optional setting that enables cookie reuse" } }, "additionalProperties": false, diff --git a/v2/.goreleaser.yml b/v2/.goreleaser.yml index c822480edd..83b95e801b 100644 --- a/v2/.goreleaser.yml +++ b/v2/.goreleaser.yml @@ -23,15 +23,15 @@ builds: flags: - -trimpath -- main: cmd/tmc/main.go - binary: tmc - id: annotate - - env: - - CGO_ENABLED=0 - - goos: [linux] - goarch: [amd64] +#- main: cmd/tmc/main.go +# binary: tmc +# id: annotate +# +# env: +# - CGO_ENABLED=0 +# +# goos: [linux] +# goarch: [amd64] archives: - format: zip diff --git a/v2/cmd/integration-test/generic.go b/v2/cmd/integration-test/generic.go new file mode 100644 index 0000000000..dfd24b1651 --- /dev/null +++ b/v2/cmd/integration-test/generic.go @@ -0,0 +1,120 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "net/http" + "net/http/httptest" + "os" + + "github.com/julienschmidt/httprouter" + + "github.com/projectdiscovery/nuclei/v2/pkg/testutils" +) + +var genericTestcases = map[string]testutils.TestCase{ + "generic/auth/certificate/http-get.yaml": &clientCertificate{}, +} + +var ( + serverCRT = `-----BEGIN CERTIFICATE----- +MIIDETCCAfkCFHA1RpGfOY5p/vQmeMQ1oRFqH+CGMA0GCSqGSIb3DQEBCwUAMEUx +CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjMwNjIxMDA0MjQ2WhcNMjMwNzIxMDA0 +MjQ2WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE +CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA3VdrKR5hmZ+vyvg6NB2dOL5vEIQ/9DevivnKWqX5mserYLMj +Wq0knVfogewZnrDe+zVC3kOogBQvYk8Z53kTY9qpJT85dMCuW4xDx0JU+cWHul9a +pzF+bvws4paCWIcsGONyocPAx5g07LbPU9civC80QkQqELo1zYiRU1bX8vRJJqbN +TW2mzl9MN3AnCAYTwq8WhVG/1QR3LPQhPR68/1LWrFefQaEWaXT2s+Xv7K7NDXro +WSba4SgKdFd6fyUVMVr/ioT1KT45TP5jbRrW5JJUTdpkiXaIucrZg39f6F5gTZGA +U7bNROUMkqrJJngN9+Hp+YH1GpkKgu9EKA30EQIDAQABMA0GCSqGSIb3DQEBCwUA +A4IBAQAw91bxiAi7DIVsKL3k4B0I+50ZKq9VMVNE3YCTPygpfuRiGQvlITZ5I8I5 +3Ok2wWltgKx6EnicHIlLg42yRj7j3mdgOLMFMrUCfJmdogwnS+k6veG3G1RHUs9r +ATfX49u/hEX2pe7Rvx2VYVIugwrQESgQ21iaf6uUMsrq6W8eYZ31as1nJKpqIGbu +W1fZMSi0RIUJP+mpVBE82IW+gJRi3uKU4HKPqyrU3dviBFdBxb3lNbh34/vdNkIw +4H2CfBxEvdwLYAhWDerlm4wWCmjkMiHfBHPBhhOICTkR25a7NFy27h/UDHjVC/6m +fGshVSBtxVPJP7kcvZ1scIctvFZZ +-----END CERTIFICATE----- +` + serverKey = `-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDdV2spHmGZn6/K ++Do0HZ04vm8QhD/0N6+K+cpapfmax6tgsyNarSSdV+iB7BmesN77NULeQ6iAFC9i +TxnneRNj2qklPzl0wK5bjEPHQlT5xYe6X1qnMX5u/CziloJYhywY43Khw8DHmDTs +ts9T1yK8LzRCRCoQujXNiJFTVtfy9Ekmps1NbabOX0w3cCcIBhPCrxaFUb/VBHcs +9CE9Hrz/UtasV59BoRZpdPaz5e/srs0NeuhZJtrhKAp0V3p/JRUxWv+KhPUpPjlM +/mNtGtbkklRN2mSJdoi5ytmDf1/oXmBNkYBTts1E5QySqskmeA334en5gfUamQqC +70QoDfQRAgMBAAECggEBALtPsHMSr9vW5Giq2m6iJRwRJGJg2NJukZLVwuYlkW7n +zGNAFgo1fkfdTfks+Z1u5rTGJPl9XkpNSrAyaqSVtNALCptnvtLMAIGe2Pj2bH0X +Kb6R1WCqJOn9ZGq4nkQW2D2Ttb2psCn458jvB9NWu6FvfRUbJFIVk1SFXx6c3pFN +kPCUudAiscaldUDCiz4FccKGXdRjq6HIeeWqvdErteb6JPTs9QXCHfBql9Esl4rK +SHt9RmAFNY+CLExHiFPBR15hHZRtiVkAVrgnPg1CPGAyVG0hGXj7YMMWpAyfFWpn +8gWVt7XJ4UX2knUwfU8p8dWe6qwf+AMrhravYJyccoUCgYEA8Ts0kHFnLga8Ewao +nyDQs5uYGG0PWkbXqnFVYnMeSbXzyC4ouInIk/eOQABCxdjy3NF9QuYvVLpfLJ+9 +a97q1Vyg6lZ4PPuK8ZcPrHFSNNaj4eWNTOMo/Qdzz4bfflTsv8vjeeMxsqb6woXV ++E23UKCPlQPf86jugZVdaMtvZKsCgYEA6uR7glji70pVoG/f3soX1vllmVTtiLnh +zYMmwPyTRDvoGgg/nGK+GCq//Xyn8D900hbX8KKqGX7ca5FGk5pOpW/QE9uLcuWK +xcy8KAc05k1u4VaS5loWKnPGWreIpj3RbCfbPs5X/jBC+fPIA4Q8Qor5ZGdqVBvW +IKejnNqasjMCgYEAqltPUbpkTWLAKweGyWnZOR3mmUlbkDt7Toje7bmyaAew82t1 +omzbU3N958DHZwVA7aSbu0TnpARB9jeRA77XRHo3wYXzP828X8R4cyVMEriJ35vG +38eESLyckrAC4SqETyZjrM4/aJT3fawaYVIw5SWegHPOEjr4xFaBMuKH9iUCgYEA +wFpC2kc374UMAcobpjIQu7aYAKyPqDuwMb+I6NjtMB9uvoKqtMIXsWqwtkBytkcA +v1p9k01hxmcg0eWxygW/CbM6zkgnNfvLXJeALbdZFo+qkVV4DrMPG8ybToalnJ1a +9hrda91GKZ4T+uQrktWjE0sDV7loVWBGRY+CaFyL+gkCgYEA3Z0j8VOLJnAKdCDp +3N74460pykwJ2suEYSJG6glXfU3fZ5VwAYjimxgD0S2VU4qK8PYBfa/oFH2vRX5p +11dWQWbfBdREO70UmJD4Pr6g3q9AF6DXLXb7dVm4y+hX065Xshk8oIuITVyO/XVK +wWqBD5GScI+Q7PLMes7aqtsDDJI= +-----END PRIVATE KEY----- +` +) + +type clientCertificate struct{} + +// Execute executes a test case and returns an error if occurred +func (h *clientCertificate) Execute(filePath string) error { + router := httprouter.New() + + router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + if len(r.TLS.PeerCertificates) == 0 { + http.Error(w, "Client certificate required", http.StatusForbidden) + return + } + + fmt.Fprintf(w, "Hello, %s!\n", r.TLS.PeerCertificates[0].Subject) + }) + + _ = os.WriteFile("server.crt", []byte(serverCRT), os.ModePerm) + _ = os.WriteFile("server.key", []byte(serverKey), os.ModePerm) + defer os.Remove("server.crt") + defer os.Remove("server.key") + + serverCert, _ := tls.LoadX509KeyPair("server.crt", "server.key") + + certPool := x509.NewCertPool() + caCert, _ := os.ReadFile("server.crt") + certPool.AppendCertsFromPEM(caCert) + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{serverCert}, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: certPool, + } + + ts := httptest.NewUnstartedServer(router) + + ts.TLS = tlsConfig + + ts.StartTLS() + defer ts.Close() + + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, + "-ca", "generic/auth/certificate/assets/server.crt", + "-cc", "generic/auth/certificate/assets/client.crt", + "-ck", "generic/auth/certificate/assets/client.key") + if err != nil { + return err + } + + return expectResultsCount(results, 1) +} diff --git a/v2/cmd/integration-test/integration-test.go b/v2/cmd/integration-test/integration-test.go index 73ee2a7652..2012f11321 100644 --- a/v2/cmd/integration-test/integration-test.go +++ b/v2/cmd/integration-test/integration-test.go @@ -39,6 +39,7 @@ var ( "offlineHttp": offlineHttpTestcases, "customConfigDir": customConfigDirTestCases, "fuzzing": fuzzingTestCases, + "generic": genericTestcases, } // For debug purposes diff --git a/v2/cmd/integration-test/workflow.go b/v2/cmd/integration-test/workflow.go index 3fa08523ba..fc22d8d8fb 100644 --- a/v2/cmd/integration-test/workflow.go +++ b/v2/cmd/integration-test/workflow.go @@ -18,6 +18,7 @@ var workflowTestcases = map[string]testutils.TestCase{ "workflow/matcher-name.yaml": &workflowMatcherName{}, "workflow/http-value-share-workflow.yaml": &workflowHttpKeyValueShare{}, "workflow/dns-value-share-workflow.yaml": &workflowDnsKeyValueShare{}, + "workflow/shared-cookie.yaml": &workflowSharedCookies{}, } type workflowBasic struct{} @@ -131,3 +132,39 @@ func (h *workflowDnsKeyValueShare) Execute(filePath string) error { // no results - ensure that the variable sharing works return expectResultsCount(results, 1) } + +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) { + cookie := &http.Cookie{Name: name, Value: name} + http.SetCookie(w, cookie) + } + + var gotCookies []string + router := httprouter.New() + router.GET("/http1", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + handleFunc("http1", w, r, p) + }) + router.GET("/http2", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + handleFunc("http2", w, r, p) + }) + router.GET("/headless1", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + handleFunc("headless1", w, r, p) + }) + router.GET("/http3", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + for _, cookie := range r.Cookies() { + gotCookies = append(gotCookies, cookie.Name) + } + }) + ts := httptest.NewServer(router) + defer ts.Close() + + _, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug, "-headless") + if err != nil { + return err + } + + return expectResultsCount(gotCookies, 3) +} diff --git a/v2/go.mod b/v2/go.mod index bb3bb4500b..ad870cc0f4 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -13,20 +13,20 @@ require ( github.com/go-rod/rod v0.113.0 github.com/gobwas/ws v1.2.1 github.com/google/go-github v17.0.0+incompatible - github.com/itchyny/gojq v0.12.12 + github.com/itchyny/gojq v0.12.13 github.com/json-iterator/go v1.1.12 github.com/julienschmidt/httprouter v1.3.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/miekg/dns v1.1.55 github.com/olekukonko/tablewriter v0.0.5 github.com/pkg/errors v0.9.1 - github.com/projectdiscovery/clistats v0.0.12 + github.com/projectdiscovery/clistats v0.0.18 github.com/projectdiscovery/fastdialer v0.0.31 github.com/projectdiscovery/hmap v0.0.13 github.com/projectdiscovery/interactsh v1.1.4 github.com/projectdiscovery/rawhttp v0.1.13 github.com/projectdiscovery/retryabledns v1.0.30 - github.com/projectdiscovery/retryablehttp-go v1.0.17 + github.com/projectdiscovery/retryablehttp-go v1.0.18 github.com/projectdiscovery/yamldoc-go v1.0.4 github.com/remeh/sizedwaitgroup v1.0.0 github.com/rs/xid v1.5.0 @@ -58,7 +58,7 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.18.27 github.com/aws/aws-sdk-go-v2/credentials v1.13.26 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.67 - github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.35.0 github.com/docker/go-units v0.5.0 github.com/fatih/structs v1.1.0 github.com/go-git/go-git/v5 v5.7.0 @@ -77,8 +77,8 @@ require ( github.com/projectdiscovery/sarif v0.0.1 github.com/projectdiscovery/tlsx v1.1.0 github.com/projectdiscovery/uncover v1.0.6-0.20230601103158-bfd7e02a5bb1 - github.com/projectdiscovery/utils v0.0.39-0.20230621170112-8dd2c290d962 - github.com/projectdiscovery/wappalyzergo v0.0.94 + github.com/projectdiscovery/utils v0.0.39 + github.com/projectdiscovery/wappalyzergo v0.0.102 github.com/stretchr/testify v1.8.4 gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/yaml.v3 v3.0.1 @@ -93,10 +93,10 @@ require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bits-and-blooms/bitset v1.3.1 // indirect github.com/bits-and-blooms/bloom/v3 v3.4.0 // indirect @@ -126,7 +126,7 @@ require ( github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/projectdiscovery/asnmap v1.0.4 // indirect github.com/projectdiscovery/cdncheck v1.0.6 // indirect - github.com/projectdiscovery/freeport v0.0.4 // indirect + github.com/projectdiscovery/freeport v0.0.5 // indirect github.com/refraction-networking/utls v1.3.2 // indirect github.com/sashabaranov/go-openai v1.11.2 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect @@ -187,7 +187,7 @@ 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.17 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mholt/acmez v1.0.4 // indirect github.com/microcosm-cc/bluemonday v1.0.24 // indirect diff --git a/v2/go.sum b/v2/go.sum index 2401376404..b37dd5f472 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -94,19 +94,23 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkf github.com/aws/aws-sdk-go-v2/internal/ini v1.3.34/go.mod h1:Etz2dj6UHYuw+Xw830KfzCfWGMzqvUTCjUj5b76GVDc= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 h1:LWA+3kDM8ly001vJ1X1waCuLJdtTl48gwkPKWy9sosI= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35/go.mod h1:0Eg1YjxE0Bhn56lx+SHJwCzhW+2JGtizsrx+lCqrfm0= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25 h1:AzwRi5OKKwo4QNqPf7TjeO+tK8AyOK3GVSwmRPo7/Cs= github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.25/go.mod h1:SUbB4wcbSEyCvqBxv/O/IBf93RbEze7U7OnoTlpPB+g= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26 h1:wscW+pnn3J1OYnanMnza5ZVYXLX4cKk5rAvUAl4Qu+c= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.26/go.mod h1:MtYiox5gvyB+OyP0Mr0Sm/yzbEAIPL9eijj/ouHAPw0= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11 h1:y2+VQzC6Zh2ojtV2LoC0MNwHWc6qXv/j2vrQtlftkdA= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28 h1:vGWm5vTpMr39tEZfQeDiDAMgk+5qsnvRny3FjLpnH5w= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.28/go.mod h1:spfrICMD6wCAhjhzHuy6DOZZ+LAIY10UxhUmLzpJTTs= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29 h1:zZSLP3v3riMOP14H7b4XP0uyfREDQOYv2cqIrvTXDNQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.29/go.mod h1:z7EjRjVwZ6pWcWdI2H64dKttvzaP99jRIj5hphW0M5U= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.27/go.mod h1:EOwBD4J4S5qYszS5/3DpkejfuK+Z5/1uzICfPaZLtqw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 h1:bkRyG4a929RCnpVSTvLM2j/T4ls015ZhhYApbmYs15s= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2 h1:NbWkRxEEIRSCqxhsHQuMiTH7yo+JZW1gp8v3elSVMTQ= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.2/go.mod h1:4tfW5l4IAB32VWCDEBxCRtR9T4BWy4I4kr1spr8NgZM= -github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1 h1:O+9nAy9Bb6bJFTpeNFtd9UfHbgxO1o4ZDAM9rQp5NsY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3 h1:dBL3StFxHtpBzJJ/mNEsjXVgfO+7jR0dAIEwLqMapEA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.14.3/go.mod h1:f1QyiAsvIv4B49DmCqrhlXqyaR+0IxMmyX+1P+AnzOM= github.com/aws/aws-sdk-go-v2/service/s3 v1.33.1/go.mod h1:J9kLNzEiHSeGMyN7238EjJmBpCniVzFda75Gxl/NqB8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.35.0 h1:ya7fmrN2fE7s1P2gaPbNg5MTkERVWfsH8ToP1YC4Z9o= +github.com/aws/aws-sdk-go-v2/service/s3 v1.35.0/go.mod h1:aVbf0sko/TsLWHx30c/uVu7c62+0EAJ3vbxaJga0xCw= github.com/aws/aws-sdk-go-v2/service/sso v1.12.10/go.mod h1:ouy2P4z6sJN70fR3ka3wD3Ro3KezSxU6eKGQI2+2fjI= github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 h1:nneMBM2p79PGWBQovYO/6Xnc2ryRMw3InnDJq1FHkSY= github.com/aws/aws-sdk-go-v2/service/sso v1.12.12/go.mod h1:HuCOxYsF21eKrerARYO6HapNeh9GBNq7fius2AcwodY= @@ -271,8 +275,8 @@ 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/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/itchyny/gojq v0.12.12 h1:x+xGI9BXqKoJQZkr95ibpe3cdrTbY8D9lonrK433rcA= -github.com/itchyny/gojq v0.12.12/go.mod h1:j+3sVkjxwd7A7Z5jrbKibgOLn0ZfLWkV+Awxr/pyzJE= +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= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jarcoal/httpmock v1.0.4 h1:jp+dy/+nonJE4g4xbVtl9QdrUNbn6/3hDT5R4nDIZnA= @@ -338,8 +342,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.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= @@ -403,16 +407,16 @@ github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= github.com/projectdiscovery/cdncheck v1.0.6 h1:bjo4oxCD1Y5972ow0LWCjUpO8KOO12j6uGfPofVpC4c= github.com/projectdiscovery/cdncheck v1.0.6/go.mod h1:NN0QRfxBzUVZJoS0lN37spElCOXHzFuvq1yg5RhTxCE= -github.com/projectdiscovery/clistats v0.0.12 h1:KLYJxpiwEFidduU4PbcwEcCQ2L7c5wrf7DI5IN5fZ+8= -github.com/projectdiscovery/clistats v0.0.12/go.mod h1:9luKJj+7Hjq3+a7g129sKWRYx4SbTdkUWZQxabn3H5Y= +github.com/projectdiscovery/clistats v0.0.18 h1:WLQNqLXsKvjoieDwXJO/1jlnxR0x9vdFaRUAR3gXfKQ= +github.com/projectdiscovery/clistats v0.0.18/go.mod h1:YUnUrMHFw+FHwUTIKr1KDUwz81x+SFjPU3xfLqXfzf0= github.com/projectdiscovery/dsl v0.0.11-0.20230621170216-97e70ffb7efd h1:16DMjd4HeACrC9CkWJkkLeSh+LYPDorwNx11BlTbonU= github.com/projectdiscovery/dsl v0.0.11-0.20230621170216-97e70ffb7efd/go.mod h1:S72Cq/lfxzkldf64Sul1G2KFbGKNgpRFFCF/FazpznM= github.com/projectdiscovery/fastdialer v0.0.31 h1:eu0wTBCWjT8dXChmBtnQaAxoFpkLdvq0VroRxZoe/M8= github.com/projectdiscovery/fastdialer v0.0.31/go.mod h1:ttLvt0xnpNQAStYYQ6ElIBHfSXHuPEiXBkLH/OLbYlc= 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.4 h1:H4VrK/7hUcC1zbg46zv9iSMBACBDpUqcHkV+FUyXISw= -github.com/projectdiscovery/freeport v0.0.4/go.mod h1:PY0bxSJ34HVy67LHIeF3uIutiCSDwOqKD8ruBkdiCwE= +github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvmyw5pFTHS8Q= +github.com/projectdiscovery/freeport v0.0.5/go.mod h1:PY0bxSJ34HVy67LHIeF3uIutiCSDwOqKD8ruBkdiCwE= github.com/projectdiscovery/goflags v0.1.10 h1:Gompf8JDy8y+5c4eWlc70KKtPuDH/hqFB3tMeHcMiKk= github.com/projectdiscovery/goflags v0.1.10/go.mod h1:MHEkqm3XgxBf5fK4gr3IXsj6VeLTq4qJYGC/4JRYQ74= github.com/projectdiscovery/gologger v1.1.10 h1:XNRdtzLTdxiFGuK9gutoL752mykzXDoii4P2yDovqck= @@ -435,8 +439,8 @@ github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917 h1:m03X4gB github.com/projectdiscovery/rdap v0.9.1-0.20221108103045-9865884d1917/go.mod h1:JxXtZC9e195awe7EynrcnBJmFoad/BNDzW9mzFkK8Sg= github.com/projectdiscovery/retryabledns v1.0.30 h1:7bc8Lq3r/qzw4LdXXAxKtQa52iGiEx1WasZLVCO6Oj0= github.com/projectdiscovery/retryabledns v1.0.30/go.mod h1:+Aqc0TjKGcTtP0HtXE8o1GzrjAHhSno6hSF+L63TBtI= -github.com/projectdiscovery/retryablehttp-go v1.0.17 h1:oppnrypatWsHxcMU5RuAcUsUu3nxBhId2CF3OBj9XJA= -github.com/projectdiscovery/retryablehttp-go v1.0.17/go.mod h1:zJh8bQdxhIsaEGnxsacvMbgiCKT4UAOr4T1kZBnSa68= +github.com/projectdiscovery/retryablehttp-go v1.0.18 h1:3IUxyIOOUVSGEBm4pV0cQSk1i/DausZdHePdGDip0Lg= +github.com/projectdiscovery/retryablehttp-go v1.0.18/go.mod h1:oE3dmYWMadFWzaIfG1IqINsYAzUWYUtdI4PJ2xo7cXg= 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= @@ -444,10 +448,10 @@ github.com/projectdiscovery/tlsx v1.1.0 h1:6L5VKpHaoqvIHN6lH9zi7jIvph1JwYMYZOIpW github.com/projectdiscovery/tlsx v1.1.0/go.mod h1:C9xTbU2t54Anmvuq+4jxevR5rzqpp6XUUtV7G9J5CTE= github.com/projectdiscovery/uncover v1.0.6-0.20230601103158-bfd7e02a5bb1 h1:Pu6LvDqn+iSlhCDKKWm1ItPc++kqqlU8OntZeB/Prak= github.com/projectdiscovery/uncover v1.0.6-0.20230601103158-bfd7e02a5bb1/go.mod h1:Drl/CWD392mKtdXJhCBPlMkM0I6671pqedFphcnK5f8= -github.com/projectdiscovery/utils v0.0.39-0.20230621170112-8dd2c290d962 h1:qQnIsYB72MmuaM9orhKpDzY0ddJKHf9Nuih0FnyV6x8= -github.com/projectdiscovery/utils v0.0.39-0.20230621170112-8dd2c290d962/go.mod h1:rrd8dTBuKEScNMLgs1Xiu8rPCVeR0QTzmRcQ5iM3ymo= -github.com/projectdiscovery/wappalyzergo v0.0.94 h1:IVRskuU95MajWCKYgvH5L67+MXDOWJDWSeBD61OsS/A= -github.com/projectdiscovery/wappalyzergo v0.0.94/go.mod h1:HvYuW0Be4JCjVds/+XAEaMSqRG9yrI97UmZq0TPk6A0= +github.com/projectdiscovery/utils v0.0.39 h1:iyi5qPilENRmFyt16qtd58pb65fUu0wAU2C0Lq5t6zo= +github.com/projectdiscovery/utils v0.0.39/go.mod h1:rrd8dTBuKEScNMLgs1Xiu8rPCVeR0QTzmRcQ5iM3ymo= +github.com/projectdiscovery/wappalyzergo v0.0.102 h1:ABjZghof2U2yzGNL+q5ouWHEardLd2o53Ukgrf8CZzE= +github.com/projectdiscovery/wappalyzergo v0.0.102/go.mod h1:4Z3DKhi75zIPMuA+qSDDWxZvnhL4qTLmDx4dxNMu7MA= github.com/projectdiscovery/yamldoc-go v1.0.4 h1:eZoESapnMw6WAHiVgRwNqvbJEfNHEH148uthhFbG5jE= github.com/projectdiscovery/yamldoc-go v1.0.4/go.mod h1:8PIPRcUD55UbtQdcfFR1hpIGRWG0P7alClXNGt1TBik= github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8= @@ -504,7 +508,6 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= @@ -657,6 +660,7 @@ 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= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -718,6 +722,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX 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= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -727,6 +732,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= diff --git a/v2/internal/runner/options.go b/v2/internal/runner/options.go index a782511a1d..7c93c46ddb 100644 --- a/v2/internal/runner/options.go +++ b/v2/internal/runner/options.go @@ -22,6 +22,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine" "github.com/projectdiscovery/nuclei/v2/pkg/types" fileutil "github.com/projectdiscovery/utils/file" + "github.com/projectdiscovery/utils/generic" logutil "github.com/projectdiscovery/utils/log" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -128,11 +129,11 @@ func validateOptions(options *types.Options) error { } // Verify if any of the client certificate options were set since it requires all three to work properly - if len(options.ClientCertFile) > 0 || len(options.ClientKeyFile) > 0 || len(options.ClientCAFile) > 0 { - if len(options.ClientCertFile) == 0 || len(options.ClientKeyFile) == 0 || len(options.ClientCAFile) == 0 { + if options.HasClientCertificates() { + if generic.EqualsAny("", options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile) { return errors.New("if a client certification option is provided, then all three must be provided") } - validateCertificatePaths([]string{options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile}) + validateCertificatePaths(options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile) } // Verify AWS secrets are passed if a S3 template bucket is passed if options.AwsBucketName != "" && options.UpdateTemplates { @@ -334,9 +335,9 @@ func validateTemplatePaths(templatesDirectory string, templatePaths, workflowPat } } -func validateCertificatePaths(certificatePaths []string) { +func validateCertificatePaths(certificatePaths ...string) { for _, certificatePath := range certificatePaths { - if _, err := os.Stat(certificatePath); os.IsNotExist(err) { + if !fileutil.FileExists(certificatePath) { // The provided path to the PEM certificate does not exist for the client authentication. As this is // required for successful authentication, log and return an error gologger.Fatal().Msgf("The given path (%s) to the certificate does not exist!", certificatePath) diff --git a/v2/pkg/catalog/config/constants.go b/v2/pkg/catalog/config/constants.go index df040cadfd..8ce1fbf952 100644 --- a/v2/pkg/catalog/config/constants.go +++ b/v2/pkg/catalog/config/constants.go @@ -7,17 +7,17 @@ import ( ) const ( - TemplateConfigFileName = ".templates-config.json" - NucleiTemplatesDirName = "nuclei-templates" + TemplateConfigFileName = ".templates-config.json" + NucleiTemplatesDirName = "nuclei-templates" OfficialNucleiTemplatesRepoName = "nuclei-templates" - NucleiIgnoreFileName = ".nuclei-ignore" - NucleiTemplatesIndexFileName = ".templates-index" // contains index of official nuclei templates - NucleiTemplatesCheckSumFileName = ".checksum" - NewTemplateAdditionsFileName = ".new-additions" - CLIConifgFileName = "config.yaml" - ReportingConfigFilename = "reporting-config.yaml" + NucleiIgnoreFileName = ".nuclei-ignore" + NucleiTemplatesIndexFileName = ".templates-index" // contains index of official nuclei templates + NucleiTemplatesCheckSumFileName = ".checksum" + NewTemplateAdditionsFileName = ".new-additions" + CLIConifgFileName = "config.yaml" + ReportingConfigFilename = "reporting-config.yaml" // Version is the current version of nuclei - Version = `v2.9.6` + Version = `v2.9.7` // Directory Names of custom templates CustomS3TemplatesDirName = "s3" CustomGithubTemplatesDirName = "github" diff --git a/v2/pkg/model/model.go b/v2/pkg/model/model.go index e57ff896d7..93e92f9664 100644 --- a/v2/pkg/model/model.go +++ b/v2/pkg/model/model.go @@ -105,4 +105,4 @@ type Classification struct { // examples: // - value: "\"cpe:/a:vendor:product:version\"" CPE string `json:"cpe,omitempty" yaml:"cpe,omitempty" jsonschema:"title=cpe for the template,description=CPE for the template,example=cpe:/a:vendor:product:version"` -} \ No newline at end of file +} diff --git a/v2/pkg/operators/extractors/extract.go b/v2/pkg/operators/extractors/extract.go index bf622ff398..19ccab38b3 100644 --- a/v2/pkg/operators/extractors/extract.go +++ b/v2/pkg/operators/extractors/extract.go @@ -175,7 +175,7 @@ func (e *Extractor) ExtractDSL(data map[string]interface{}) map[string]struct{} for _, compiledExpression := range e.dslCompiled { result, err := compiledExpression.Evaluate(data) // ignore errors that are related to missing parameters - // eg: dns dsl can have all the parameters that are not present + // eg: dns dsl can have all the parameters that are not present if err != nil && !strings.HasPrefix(err.Error(), "No parameter") { return results } diff --git a/v2/pkg/parsers/parser.go b/v2/pkg/parsers/parser.go index b0f78ecee5..2038395cec 100644 --- a/v2/pkg/parsers/parser.go +++ b/v2/pkg/parsers/parser.go @@ -80,7 +80,6 @@ func isTemplateInfoMetadataMatch(tagFilter *filter.TagFilter, template *template match, err := tagFilter.Match(template, extraTags) if err == filter.ErrExcluded { - return false, filter.ErrExcluded } diff --git a/v2/pkg/progress/progress.go b/v2/pkg/progress/progress.go index 6ef593329c..014be676af 100644 --- a/v2/pkg/progress/progress.go +++ b/v2/pkg/progress/progress.go @@ -99,15 +99,23 @@ func (p *StatsTicker) Init(hostCount int64, rulesCount int, requestCount int64) p.stats.AddCounter("total", uint64(requestCount)) if p.active { - var printCallbackFunc clistats.PrintCallback + var printCallbackFunc clistats.DynamicCallback if p.outputJSON { printCallbackFunc = printCallbackJSON } else { printCallbackFunc = p.makePrintCallback() } - if err := p.stats.Start(printCallbackFunc, p.tickDuration); err != nil { + p.stats.AddDynamic("summary", printCallbackFunc) + if err := p.stats.Start(); err != nil { gologger.Warning().Msgf("Couldn't start statistics: %s", err) } + + p.stats.GetStatResponse(p.tickDuration, func(s string, err error) error { + if err != nil { + gologger.Warning().Msgf("Could not read statistics: %s\n", err) + } + return nil + }) } } @@ -145,8 +153,8 @@ func (p *StatsTicker) IncrementFailedRequestsBy(count int64) { p.stats.IncrementCounter("errors", int(count)) } -func (p *StatsTicker) makePrintCallback() func(stats clistats.StatisticsClient) { - return func(stats clistats.StatisticsClient) { +func (p *StatsTicker) makePrintCallback() func(stats clistats.StatisticsClient) interface{} { + return func(stats clistats.StatisticsClient) interface{} { builder := &strings.Builder{} var duration time.Duration @@ -209,14 +217,16 @@ func (p *StatsTicker) makePrintCallback() func(stats clistats.StatisticsClient) } fmt.Fprintf(os.Stderr, "%s", builder.String()) + return builder.String() } } -func printCallbackJSON(stats clistats.StatisticsClient) { +func printCallbackJSON(stats clistats.StatisticsClient) interface{} { builder := &strings.Builder{} if err := json.NewEncoder(builder).Encode(metricsMap(stats)); err == nil { fmt.Fprintf(os.Stderr, "%s", builder.String()) } + return builder.String() } func metricsMap(stats clistats.StatisticsClient) map[string]interface{} { diff --git a/v2/pkg/protocols/common/fuzz/execute.go b/v2/pkg/protocols/common/fuzz/execute.go index 3c7173b9d7..a16c7107e5 100644 --- a/v2/pkg/protocols/common/fuzz/execute.go +++ b/v2/pkg/protocols/common/fuzz/execute.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" "github.com/projectdiscovery/retryablehttp-go" urlutil "github.com/projectdiscovery/utils/url" @@ -13,8 +14,8 @@ import ( // ExecuteRuleInput is the input for rule Execute function type ExecuteRuleInput struct { - // URL is the URL for the request - URL *urlutil.URL + // 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 @@ -41,7 +42,7 @@ type GeneratedRequest struct { // Input is not thread safe and should not be shared between concurrent // goroutines. func (rule *Rule) Execute(input *ExecuteRuleInput) error { - if !rule.isExecutable(input.URL) { + if !rule.isExecutable(input.Input) { return nil } baseValues := input.Values @@ -69,7 +70,11 @@ func (rule *Rule) Execute(input *ExecuteRuleInput) error { } // isExecutable returns true if the rule can be executed based on provided input -func (rule *Rule) isExecutable(parsed *urlutil.URL) bool { +func (rule *Rule) isExecutable(input *contextargs.Context) bool { + parsed, err := urlutil.Parse(input.MetaInput.Input) + if err != nil { + return false + } if len(parsed.Query()) > 0 && rule.partType == queryPartType { return true } diff --git a/v2/pkg/protocols/common/fuzz/execute_test.go b/v2/pkg/protocols/common/fuzz/execute_test.go index 7f44f94035..88582e1b35 100644 --- a/v2/pkg/protocols/common/fuzz/execute_test.go +++ b/v2/pkg/protocols/common/fuzz/execute_test.go @@ -3,7 +3,7 @@ package fuzz import ( "testing" - urlutil "github.com/projectdiscovery/utils/url" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" "github.com/stretchr/testify/require" ) @@ -12,11 +12,11 @@ func TestRuleIsExecutable(t *testing.T) { err := rule.Compile(nil, nil) require.NoError(t, err, "could not compile rule") - parsed, _ := urlutil.Parse("https://example.com/?url=localhost") - result := rule.isExecutable(parsed) + input := contextargs.NewWithInput("https://example.com/?url=localhost") + result := rule.isExecutable(input) require.True(t, result, "could not get correct result") - parsed, _ = urlutil.Parse("https://example.com/") - result = rule.isExecutable(parsed) + input = contextargs.NewWithInput("https://example.com/") + result = rule.isExecutable(input) require.False(t, result, "could not get correct result") } diff --git a/v2/pkg/protocols/common/fuzz/parts.go b/v2/pkg/protocols/common/fuzz/parts.go index 9c0c43025a..0576f302f3 100644 --- a/v2/pkg/protocols/common/fuzz/parts.go +++ b/v2/pkg/protocols/common/fuzz/parts.go @@ -24,16 +24,20 @@ func (rule *Rule) executePartRule(input *ExecuteRuleInput, payload string) error // executeQueryPartRule executes query part rules func (rule *Rule) executeQueryPartRule(input *ExecuteRuleInput, payload string) error { - requestURL := input.URL.Clone() + requestURL, err := urlutil.Parse(input.Input.MetaInput.Input) + if err != nil { + return err + } + origRequestURL := requestURL.Clone() temp := urlutil.Params{} - for k, v := range input.URL.Query() { + for k, v := range origRequestURL.Query() { // this has to be a deep copy x := []string{} x = append(x, v...) temp[k] = x } - for key, values := range input.URL.Query() { + for key, values := range origRequestURL.Query() { for i, value := range values { if !rule.matchKeyOrValue(key, value) { continue diff --git a/v2/pkg/protocols/common/fuzz/parts_test.go b/v2/pkg/protocols/common/fuzz/parts_test.go index e4402c946a..e1855a3caf 100644 --- a/v2/pkg/protocols/common/fuzz/parts_test.go +++ b/v2/pkg/protocols/common/fuzz/parts_test.go @@ -4,13 +4,13 @@ import ( "testing" "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" - urlutil "github.com/projectdiscovery/utils/url" "github.com/stretchr/testify/require" ) func TestExecuteQueryPartRule(t *testing.T) { - parsed, _ := urlutil.Parse("http://localhost:8080/?url=localhost&mode=multiple&file=passwdfile") + URL := "http://localhost:8080/?url=localhost&mode=multiple&file=passwdfile" options := &protocols.ExecutorOptions{ Interactsh: &interactsh.Client{}, } @@ -22,8 +22,9 @@ func TestExecuteQueryPartRule(t *testing.T) { options: options, } var generatedURL []string + input := contextargs.NewWithInput(URL) err := rule.executeQueryPartRule(&ExecuteRuleInput{ - URL: parsed, + Input: input, Callback: func(gr GeneratedRequest) bool { generatedURL = append(generatedURL, gr.Request.URL.String()) return true @@ -44,8 +45,9 @@ func TestExecuteQueryPartRule(t *testing.T) { options: options, } var generatedURL string + input := contextargs.NewWithInput(URL) err := rule.executeQueryPartRule(&ExecuteRuleInput{ - URL: parsed, + Input: input, Callback: func(gr GeneratedRequest) bool { generatedURL = gr.Request.URL.String() return true diff --git a/v2/pkg/protocols/headless/engine/page.go b/v2/pkg/protocols/headless/engine/page.go index 96bbb15af0..e23d2eb253 100644 --- a/v2/pkg/protocols/headless/engine/page.go +++ b/v2/pkg/protocols/headless/engine/page.go @@ -1,7 +1,9 @@ package engine import ( + "bufio" "fmt" + "net/http" "net/url" "strings" "sync" @@ -9,10 +11,14 @@ import ( "github.com/go-rod/rod" "github.com/go-rod/rod/lib/proto" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" ) // Page is a single page in an isolated browser instance type Page struct { + input *contextargs.Context + options *Options page *rod.Page rules []rule instance *Instance @@ -30,13 +36,19 @@ type HistoryData struct { RawResponse string } +// Options contains additional configuration options for the browser instance +type Options struct { + Timeout time.Duration + CookieReuse bool +} + // Run runs a list of actions by creating a new page in the browser. -func (i *Instance) Run(baseURL *url.URL, actions []*Action, payloads map[string]interface{}, timeout time.Duration) (map[string]string, *Page, error) { +func (i *Instance) Run(input *contextargs.Context, actions []*Action, payloads map[string]interface{}, options *Options) (map[string]string, *Page, error) { page, err := i.engine.Page(proto.TargetCreateTarget{}) if err != nil { return nil, nil, err } - page = page.Timeout(timeout) + page = page.Timeout(options.Timeout) if i.browser.customAgent != "" { if userAgentErr := page.SetUserAgent(&proto.NetworkSetUserAgentOverride{UserAgent: i.browser.customAgent}); userAgentErr != nil { @@ -44,7 +56,14 @@ func (i *Instance) Run(baseURL *url.URL, actions []*Action, payloads map[string] } } - createdPage := &Page{page: page, instance: i, mutex: &sync.RWMutex{}, payloads: payloads} + createdPage := &Page{ + options: options, + page: page, + input: input, + instance: i, + mutex: &sync.RWMutex{}, + payloads: payloads, + } // in case the page has request/response modification rules - enable global hijacking if createdPage.hasModificationRules() || containsModificationActions(actions...) { @@ -79,18 +98,76 @@ func (i *Instance) Run(baseURL *url.URL, actions []*Action, payloads map[string] return nil, nil, err } - //FIXME: this is a hack, make sure to fix this in the future. See: https://github.com/go-rod/rod/issues/188 - var e proto.NetworkResponseReceived - wait := page.WaitEvent(&e) + // inject cookies + // each http request is performed via the native go http client + // we first inject the shared cookies + URL, err := url.Parse(input.MetaInput.Input) + if err != nil { + return nil, nil, err + } - data, err := createdPage.ExecuteActions(baseURL, actions) + if options.CookieReuse { + if cookies := input.CookieJar.Cookies(URL); len(cookies) > 0 { + var NetworkCookies []*proto.NetworkCookie + for _, cookie := range cookies { + networkCookie := &proto.NetworkCookie{ + Name: cookie.Name, + Value: cookie.Value, + Domain: cookie.Domain, + Path: cookie.Path, + HTTPOnly: cookie.HttpOnly, + Secure: cookie.Secure, + Expires: proto.TimeSinceEpoch(cookie.Expires.Unix()), + SameSite: proto.NetworkCookieSameSite(GetSameSite(cookie)), + Priority: proto.NetworkCookiePriorityLow, + } + NetworkCookies = append(NetworkCookies, networkCookie) + } + params := proto.CookiesToParams(NetworkCookies) + for _, param := range params { + param.URL = input.MetaInput.Input + } + err := page.SetCookies(params) + if err != nil { + return nil, nil, err + } + } + } + + data, err := createdPage.ExecuteActions(input, actions) if err != nil { return nil, nil, err } - wait() - data["header"] = headersToString(e.Response.Headers) - data["status_code"] = fmt.Sprint(e.Response.Status) + if options.CookieReuse { + // at the end of actions pull out updated cookies from the browser and inject them into the shared cookie jar + if cookies, err := page.Cookies([]string{URL.String()}); options.CookieReuse && err == nil && len(cookies) > 0 { + var httpCookies []*http.Cookie + for _, cookie := range cookies { + httpCookie := &http.Cookie{ + Name: cookie.Name, + Value: cookie.Value, + Domain: cookie.Domain, + Path: cookie.Path, + HttpOnly: cookie.HTTPOnly, + Secure: cookie.Secure, + } + httpCookies = append(httpCookies, httpCookie) + } + input.CookieJar.SetCookies(URL, httpCookies) + } + } + + // The first item of history data will contain the very first request from the browser + // we assume it's the one matching the initial URL + if len(createdPage.History) > 0 { + firstItem := createdPage.History[0] + if resp, err := http.ReadResponse(bufio.NewReader(strings.NewReader(firstItem.RawResponse)), nil); err == nil { + data["header"] = utils.HeadersToString(resp.Header) + data["status_code"] = fmt.Sprint(resp.StatusCode) + resp.Body.Close() + } + } return data, createdPage, nil } @@ -189,14 +266,17 @@ func containsAnyModificationActionType(actionTypes ...ActionType) bool { return false } -// headersToString converts network headers to string -func headersToString(headers proto.NetworkHeaders) string { - builder := &strings.Builder{} - for header, value := range headers { - builder.WriteString(header) - builder.WriteString(": ") - builder.WriteString(value.String()) - builder.WriteRune('\n') +func GetSameSite(cookie *http.Cookie) string { + switch cookie.SameSite { + case http.SameSiteNoneMode: + return "none" + case http.SameSiteLaxMode: + return "lax" + case http.SameSiteStrictMode: + return "strict" + case http.SameSiteDefaultMode: + fallthrough + default: + return "" } - return builder.String() } diff --git a/v2/pkg/protocols/headless/engine/page_actions.go b/v2/pkg/protocols/headless/engine/page_actions.go index 294abbe6a7..b96d4f264d 100644 --- a/v2/pkg/protocols/headless/engine/page_actions.go +++ b/v2/pkg/protocols/headless/engine/page_actions.go @@ -18,6 +18,7 @@ import ( "github.com/go-rod/rod/lib/utils" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators" errorutil "github.com/projectdiscovery/utils/errors" fileutil "github.com/projectdiscovery/utils/file" @@ -38,8 +39,11 @@ const ( ) // ExecuteActions executes a list of actions on a page. -func (p *Page) ExecuteActions(baseURL *url.URL, actions []*Action) (map[string]string, error) { - var err error +func (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action) (map[string]string, error) { + baseURL, err := url.Parse(input.MetaInput.Input) + if err != nil { + return nil, err + } outData := make(map[string]string) for _, act := range actions { @@ -213,7 +217,7 @@ func (p *Page) ActionDeleteHeader(act *Action, out map[string]string /*TODO revi } // ActionSetBody executes a SetBody action. -func (p *Page) ActionSetBody(act *Action, out map[string]string /*TODO review unused parameter*/) error { +func (p *Page) ActionSetBody(act *Action, out map[string]string) error { in := p.getActionArgWithDefaultValues(act, "part") args := make(map[string]string) @@ -233,7 +237,7 @@ func (p *Page) ActionSetMethod(act *Action, out map[string]string) error { } // NavigateURL executes an ActionLoadURL actions loading a URL for the page. -func (p *Page) NavigateURL(action *Action, out map[string]string, parsed *url.URL /*TODO review unused parameter*/) error { +func (p *Page) NavigateURL(action *Action, out map[string]string, parsed *url.URL) error { URL := p.getActionArgWithDefaultValues(action, "url") if URL == "" { return errinvalidArguments diff --git a/v2/pkg/protocols/headless/engine/page_actions_test.go b/v2/pkg/protocols/headless/engine/page_actions_test.go index 78602b4aca..3524b47d86 100644 --- a/v2/pkg/protocols/headless/engine/page_actions_test.go +++ b/v2/pkg/protocols/headless/engine/page_actions_test.go @@ -5,8 +5,8 @@ import ( "io" "math/rand" "net/http" + "net/http/cookiejar" "net/http/httptest" - "net/url" "os" "path/filepath" "strconv" @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v2/pkg/testutils/testheadless" "github.com/projectdiscovery/nuclei/v2/pkg/types" @@ -564,9 +565,11 @@ func testHeadless(t *testing.T, actions []*Action, timeout time.Duration, handle ts := httptest.NewServer(http.HandlerFunc(handler)) defer ts.Close() - parsed, err := url.Parse(ts.URL) - require.Nil(t, err, "could not parse URL") - extractedData, page, err := instance.Run(parsed, actions, nil, timeout) + input := contextargs.NewWithInput(ts.URL) + input.CookieJar, err = cookiejar.New(nil) + require.Nil(t, err) + + extractedData, page, err := instance.Run(input, actions, nil, &Options{Timeout: timeout}) assert(page, err, extractedData) if page != nil { diff --git a/v2/pkg/protocols/headless/engine/rules.go b/v2/pkg/protocols/headless/engine/rules.go index 2768cc99cc..22c0057fe0 100644 --- a/v2/pkg/protocols/headless/engine/rules.go +++ b/v2/pkg/protocols/headless/engine/rules.go @@ -35,8 +35,26 @@ func (p *Page) routingRuleHandler(ctx *rod.Hijack) { ctx.Request.SetBody(body) } } + + if p.options.CookieReuse { + // each http request is performed via the native go http client + // we first inject the shared cookies + if cookies := p.input.CookieJar.Cookies(ctx.Request.URL()); len(cookies) > 0 { + p.instance.browser.httpclient.Jar.SetCookies(ctx.Request.URL(), cookies) + } + } + + // perform the request _ = ctx.LoadResponse(p.instance.browser.httpclient, true) + if p.options.CookieReuse { + // retrieve the updated cookies from the native http client and inject them into the shared cookie jar + // keeps existing one if not present + if cookies := p.instance.browser.httpclient.Jar.Cookies(ctx.Request.URL()); len(cookies) > 0 { + p.input.CookieJar.SetCookies(ctx.Request.URL(), cookies) + } + } + for _, rule := range p.rules { if rule.Part != "response" { continue diff --git a/v2/pkg/protocols/headless/headless.go b/v2/pkg/protocols/headless/headless.go index d4ba60b4fc..06203d3edc 100644 --- a/v2/pkg/protocols/headless/headless.go +++ b/v2/pkg/protocols/headless/headless.go @@ -58,6 +58,10 @@ type Request struct { // Fuzzing describes schema to fuzz headless requests Fuzzing []*fuzz.Rule `yaml:"fuzzing,omitempty" json:"fuzzing,omitempty" jsonschema:"title=fuzzin rules for http fuzzing,description=Fuzzing describes rule schema to fuzz headless requests"` + + // description: | + // CookieReuse is an optional setting that enables cookie reuse + CookieReuse bool `yaml:"cookie-reuse,omitempty" json:"cookie-reuse,omitempty" jsonschema:"title=optional cookie reuse enable,description=Optional setting that enables cookie reuse"` } // RequestPartDefinitions contains a mapping of request part definitions and their diff --git a/v2/pkg/protocols/headless/request.go b/v2/pkg/protocols/headless/request.go index 814769d11a..9365d142cf 100644 --- a/v2/pkg/protocols/headless/request.go +++ b/v2/pkg/protocols/headless/request.go @@ -19,6 +19,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine" protocolutils "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" urlutil "github.com/projectdiscovery/utils/url" @@ -35,7 +36,6 @@ func (request *Request) Type() templateTypes.ProtocolType { // ExecuteWithResults executes the protocol requests and returns results instead of writing them. func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error { - inputURL := input.MetaInput.Input if request.options.Browser.UserAgent() == "" { request.options.Browser.SetUserAgent(request.compiledUserAgent) } @@ -56,7 +56,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, } // verify if fuzz elaboration was requested if len(request.Fuzzing) > 0 { - return request.executeFuzzingRule(inputURL, payloads, previous, wrappedCallback) + return request.executeFuzzingRule(input, payloads, previous, wrappedCallback) } if request.generator != nil { iterator := request.generator.NewIterator() @@ -69,23 +69,23 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, return nil } value = generators.MergeMaps(value, payloads) - if err := request.executeRequestWithPayloads(inputURL, value, previous, wrappedCallback); err != nil { + if err := request.executeRequestWithPayloads(input, value, previous, wrappedCallback); err != nil { return err } } } else { value := maps.Clone(payloads) - if err := request.executeRequestWithPayloads(inputURL, value, previous, wrappedCallback); err != nil { + if err := request.executeRequestWithPayloads(input, value, previous, wrappedCallback); err != nil { return err } } return nil } -func (request *Request) executeRequestWithPayloads(inputURL string, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error { +func (request *Request) executeRequestWithPayloads(input *contextargs.Context, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error { instance, err := request.options.Browser.NewInstance() if err != nil { - request.options.Output.Request(request.options.TemplatePath, inputURL, request.Type().String(), err) + request.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err) request.options.Progress.IncrementFailedRequestsBy(1) return errors.Wrap(err, errCouldGetHtmlElement) } @@ -97,32 +97,39 @@ func (request *Request) executeRequestWithPayloads(inputURL string, payloads map instance.SetInteractsh(request.options.Interactsh) - parsedURL, err := url.Parse(inputURL) - if err != nil { - request.options.Output.Request(request.options.TemplatePath, inputURL, request.Type().String(), err) + if _, err := url.Parse(input.MetaInput.Input); err != nil { + request.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err) request.options.Progress.IncrementFailedRequestsBy(1) return errors.Wrap(err, errCouldGetHtmlElement) } - timeout := time.Duration(request.options.Options.PageTimeout) * time.Second - out, page, err := instance.Run(parsedURL, request.Steps, payloads, timeout) + options := &engine.Options{ + Timeout: time.Duration(request.options.Options.PageTimeout) * time.Second, + CookieReuse: request.CookieReuse, + } + + if options.CookieReuse && input.CookieJar == nil { + return errors.New("cookie-reuse set but cookie-jar is nil") + } + + out, page, err := instance.Run(input, request.Steps, payloads, options) if err != nil { - request.options.Output.Request(request.options.TemplatePath, inputURL, request.Type().String(), err) + request.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err) request.options.Progress.IncrementFailedRequestsBy(1) return errors.Wrap(err, errCouldGetHtmlElement) } defer page.Close() - request.options.Output.Request(request.options.TemplatePath, inputURL, request.Type().String(), nil) + request.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), nil) request.options.Progress.IncrementRequests() - gologger.Verbose().Msgf("Sent Headless request to %s", inputURL) + gologger.Verbose().Msgf("Sent Headless request to %s", input.MetaInput.Input) reqBuilder := &strings.Builder{} if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.DebugResponse { - gologger.Info().Msgf("[%s] Dumped Headless request for %s", request.options.TemplateID, inputURL) + gologger.Info().Msgf("[%s] Dumped Headless request for %s", request.options.TemplateID, input.MetaInput.Input) for _, act := range request.Steps { actStepStr := act.String() - actStepStr = strings.ReplaceAll(actStepStr, "{{BaseURL}}", inputURL) + actStepStr = strings.ReplaceAll(actStepStr, "{{BaseURL}}", input.MetaInput.Input) reqBuilder.WriteString("\t" + actStepStr + "\n") } gologger.Debug().Msgf(reqBuilder.String()) @@ -135,7 +142,7 @@ func (request *Request) executeRequestWithPayloads(inputURL string, payloads map responseBody, _ = html.HTML() } - outputEvent := request.responseToDSLMap(responseBody, out["header"], out["status_code"], reqBuilder.String(), inputURL, inputURL, page.DumpHistory()) + outputEvent := request.responseToDSLMap(responseBody, out["header"], out["status_code"], reqBuilder.String(), input.MetaInput.Input, input.MetaInput.Input, page.DumpHistory()) for k, v := range out { outputEvent[k] = v } @@ -161,7 +168,7 @@ func (request *Request) executeRequestWithPayloads(inputURL string, payloads map event.UsesInteractsh = true } - dumpResponse(event, request.options, responseBody, inputURL) + dumpResponse(event, request.options, responseBody, input.MetaInput.Input) return nil } @@ -174,26 +181,27 @@ func dumpResponse(event *output.InternalWrappedEvent, requestOptions *protocols. } // executeFuzzingRule executes a fuzzing rule in the template request -func (request *Request) executeFuzzingRule(inputURL string, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error { +func (request *Request) executeFuzzingRule(input *contextargs.Context, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error { // check for operator matches by wrapping callback gotmatches := false fuzzRequestCallback := func(gr fuzz.GeneratedRequest) bool { if gotmatches && (request.StopAtFirstMatch || request.options.Options.StopAtFirstMatch || request.options.StopAtFirstMatch) { return true } - if err := request.executeRequestWithPayloads(gr.Request.URL.String(), gr.DynamicValues, previous, callback); err != nil { + newInput := input.Clone() + newInput.MetaInput.Input = gr.Request.URL.String() + if err := request.executeRequestWithPayloads(newInput, gr.DynamicValues, previous, callback); err != nil { return false } return true } - parsedURL, err := urlutil.Parse(inputURL) - if err != nil { + if _, err := urlutil.Parse(input.MetaInput.Input); err != nil { return errors.Wrap(err, "could not parse url") } for _, rule := range request.Fuzzing { err := rule.Execute(&fuzz.ExecuteRuleInput{ - URL: parsedURL, + Input: input, Callback: fuzzRequestCallback, Values: payloads, BaseRequest: nil, diff --git a/v2/pkg/protocols/http/httpclientpool/clientpool.go b/v2/pkg/protocols/http/httpclientpool/clientpool.go index ececb0838a..2cbcdfa33e 100644 --- a/v2/pkg/protocols/http/httpclientpool/clientpool.go +++ b/v2/pkg/protocols/http/httpclientpool/clientpool.go @@ -229,6 +229,9 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl ForceAttemptHTTP2: options.ForceAttemptHTTP2, DialContext: Dialer.Dial, DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + if options.HasClientCertificates() { + return Dialer.DialTLSWithConfig(ctx, network, addr, tlsConfig) + } if options.TlsImpersonate { return Dialer.DialTLSWithConfigImpersonate(ctx, network, addr, tlsConfig, impersonate.Random, nil) } diff --git a/v2/pkg/protocols/http/request.go b/v2/pkg/protocols/http/request.go index bbadc187d6..304eead2b0 100644 --- a/v2/pkg/protocols/http/request.go +++ b/v2/pkg/protocols/http/request.go @@ -228,8 +228,7 @@ func (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValu // executeFuzzingRule executes fuzzing request for a URL func (request *Request) executeFuzzingRule(input *contextargs.Context, previous output.InternalEvent, callback protocols.OutputEventCallback) error { - parsed, err := urlutil.Parse(input.MetaInput.Input) - if err != nil { + if _, err := urlutil.Parse(input.MetaInput.Input); err != nil { return errors.Wrap(err, "could not parse url") } fuzzRequestCallback := func(gr fuzz.GeneratedRequest) bool { @@ -297,7 +296,7 @@ func (request *Request) executeFuzzingRule(input *contextargs.Context, previous } for _, rule := range request.Fuzzing { err = rule.Execute(&fuzz.ExecuteRuleInput{ - URL: parsed, + Input: input, Callback: fuzzRequestCallback, Values: generated.dynamicValues, BaseRequest: generated.request, diff --git a/v2/pkg/protocols/offlinehttp/request.go b/v2/pkg/protocols/offlinehttp/request.go index 7bafa3a7bd..2bcbeacc11 100644 --- a/v2/pkg/protocols/offlinehttp/request.go +++ b/v2/pkg/protocols/offlinehttp/request.go @@ -2,10 +2,8 @@ package offlinehttp import ( "io" - "net/http" "net/http/httputil" "os" - "strings" "github.com/pkg/errors" "github.com/remeh/sizedwaitgroup" @@ -16,6 +14,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" ) @@ -86,7 +85,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata return } - outputEvent := request.responseToDSLMap(resp, data, data, data, tostring.UnsafeToString(dumpedResponse), tostring.UnsafeToString(body), headersToString(resp.Header), 0, nil) + outputEvent := request.responseToDSLMap(resp, data, data, data, tostring.UnsafeToString(dumpedResponse), tostring.UnsafeToString(body), utils.HeadersToString(resp.Header), 0, nil) outputEvent["ip"] = "" for k, v := range previous { outputEvent[k] = v @@ -105,25 +104,3 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, metadata request.options.Progress.IncrementRequests() return nil } - -// headersToString converts http headers to string -func headersToString(headers http.Header) string { - builder := &strings.Builder{} - - for header, values := range headers { - builder.WriteString(header) - builder.WriteString(": ") - - for i, value := range values { - builder.WriteString(value) - - if i != len(values)-1 { - builder.WriteRune('\n') - builder.WriteString(header) - builder.WriteString(": ") - } - } - builder.WriteRune('\n') - } - return builder.String() -} diff --git a/v2/pkg/protocols/utils/utils.go b/v2/pkg/protocols/utils/utils.go index cdc0b367d5..505a0a3b7e 100644 --- a/v2/pkg/protocols/utils/utils.go +++ b/v2/pkg/protocols/utils/utils.go @@ -3,6 +3,7 @@ package utils import ( "crypto/tls" "crypto/x509" + "net/http" "os" "strings" @@ -46,3 +47,25 @@ func CalculateContentLength(contentLength, bodyLength int64) int64 { } return bodyLength } + +// headersToString converts http headers to string +func HeadersToString(headers http.Header) string { + builder := &strings.Builder{} + + for header, values := range headers { + builder.WriteString(header) + builder.WriteString(": ") + + for i, value := range values { + builder.WriteString(value) + + if i != len(values)-1 { + builder.WriteRune('\n') + builder.WriteString(header) + builder.WriteString(": ") + } + } + builder.WriteRune('\n') + } + return builder.String() +} diff --git a/v2/pkg/templates/templates_doc.go b/v2/pkg/templates/templates_doc.go index ee857fd9bf..25b094b01f 100644 --- a/v2/pkg/templates/templates_doc.go +++ b/v2/pkg/templates/templates_doc.go @@ -1212,7 +1212,7 @@ func init() { Value: "Headless response received from client (default)", }, } - HEADLESSRequestDoc.Fields = make([]encoder.Doc, 8) + HEADLESSRequestDoc.Fields = make([]encoder.Doc, 9) HEADLESSRequestDoc.Fields[0].Name = "id" HEADLESSRequestDoc.Fields[0].Type = "string" HEADLESSRequestDoc.Fields[0].Note = "" @@ -1253,6 +1253,11 @@ func init() { HEADLESSRequestDoc.Fields[7].Note = "" HEADLESSRequestDoc.Fields[7].Description = "Fuzzing describes schema to fuzz headless requests" HEADLESSRequestDoc.Fields[7].Comments[encoder.LineComment] = " Fuzzing describes schema to fuzz headless requests" + HEADLESSRequestDoc.Fields[8].Name = "cookie-reuse" + HEADLESSRequestDoc.Fields[8].Type = "bool" + HEADLESSRequestDoc.Fields[8].Note = "" + HEADLESSRequestDoc.Fields[8].Description = "CookieReuse is an optional setting that enables cookie reuse" + HEADLESSRequestDoc.Fields[8].Comments[encoder.LineComment] = "CookieReuse is an optional setting that enables cookie reuse" ENGINEActionDoc.Type = "engine.Action" ENGINEActionDoc.Comments[encoder.LineComment] = " Action is an action taken by the browser to reach a navigation" diff --git a/v2/pkg/types/types.go b/v2/pkg/types/types.go index 0018a804f9..02a9150185 100644 --- a/v2/pkg/types/types.go +++ b/v2/pkg/types/types.go @@ -381,6 +381,11 @@ func (options *Options) ShouldFollowHTTPRedirects() bool { return options.FollowRedirects || options.FollowHostRedirects } +// HasClientCertificates determines if any client certificate was specified +func (options *Options) HasClientCertificates() bool { + return options.ClientCertFile != "" || options.ClientCAFile != "" || options.ClientKeyFile != "" +} + // DefaultOptions returns default options for nuclei func DefaultOptions() *Options { return &Options{