mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-17 19:35:27 +00:00
* chore: fix non-constant fmt string in call Signed-off-by: Dwi Siswanto <git@dw1.io> * build: bump all direct modules Signed-off-by: Dwi Siswanto <git@dw1.io> * chore(hosterrorscache): update import path Signed-off-by: Dwi Siswanto <git@dw1.io> * fix(charts): break changes Signed-off-by: Dwi Siswanto <git@dw1.io> * build: pinned `github.com/zmap/zcrypto` to v0.0.0-20240512203510-0fef58d9a9db Signed-off-by: Dwi Siswanto <git@dw1.io> * chore: golangci-lint auto fixes Signed-off-by: Dwi Siswanto <git@dw1.io> * chore: satisfy lints Signed-off-by: Dwi Siswanto <git@dw1.io> * build: migrate `github.com/xanzy/go-gitlab` => `gitlab.com/gitlab-org/api/client-go` Signed-off-by: Dwi Siswanto <git@dw1.io> * feat(json): update build constraints Signed-off-by: Dwi Siswanto <git@dw1.io> * chore: dont panicking on close err Signed-off-by: Dwi Siswanto <git@dw1.io> --------- Signed-off-by: Dwi Siswanto <git@dw1.io>
307 lines
10 KiB
Go
307 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
|
|
sliceutil "github.com/projectdiscovery/utils/slice"
|
|
)
|
|
|
|
var workflowTestcases = []TestCaseInfo{
|
|
{Path: "workflow/basic.yaml", TestCase: &workflowBasic{}},
|
|
{Path: "workflow/condition-matched.yaml", TestCase: &workflowConditionMatched{}},
|
|
{Path: "workflow/condition-unmatched.yaml", TestCase: &workflowConditionUnmatch{}},
|
|
{Path: "workflow/matcher-name.yaml", TestCase: &workflowMatcherName{}},
|
|
{Path: "workflow/complex-conditions.yaml", TestCase: &workflowComplexConditions{}},
|
|
{Path: "workflow/http-value-share-workflow.yaml", TestCase: &workflowHttpKeyValueShare{}},
|
|
{Path: "workflow/dns-value-share-workflow.yaml", TestCase: &workflowDnsKeyValueShare{}},
|
|
{Path: "workflow/code-value-share-workflow.yaml", TestCase: &workflowCodeKeyValueShare{}, DisableOn: isCodeDisabled}, // isCodeDisabled declared in code.go
|
|
{Path: "workflow/multiprotocol-value-share-workflow.yaml", TestCase: &workflowMultiProtocolKeyValueShare{}},
|
|
{Path: "workflow/multimatch-value-share-workflow.yaml", TestCase: &workflowMultiMatchKeyValueShare{}},
|
|
{Path: "workflow/shared-cookie.yaml", TestCase: &workflowSharedCookies{}},
|
|
}
|
|
|
|
func init() {
|
|
// sign code templates (unless they are disabled)
|
|
if !isCodeDisabled() {
|
|
// allow local file access to load content of file references in template
|
|
// in order to sign them for testing purposes
|
|
templates.TemplateSignerLFA()
|
|
|
|
// testCertFile and testKeyFile are declared in code.go
|
|
tsigner, err := signer.NewTemplateSignerFromFiles(testCertFile, testKeyFile)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// only the code templates are necessary to be signed
|
|
var templatesToSign = []string{
|
|
"workflow/code-template-1.yaml",
|
|
"workflow/code-template-2.yaml",
|
|
}
|
|
for _, templatePath := range templatesToSign {
|
|
if err := templates.SignTemplate(tsigner, templatePath); err != nil {
|
|
log.Fatalf("Could not sign template %v got: %s\n", templatePath, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type workflowBasic struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowBasic) Execute(filePath string) error {
|
|
router := httprouter.New()
|
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "This is test matcher text")
|
|
})
|
|
ts := httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return expectResultsCount(results, 2)
|
|
}
|
|
|
|
type workflowConditionMatched struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowConditionMatched) Execute(filePath string) error {
|
|
router := httprouter.New()
|
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "This is test matcher text")
|
|
})
|
|
ts := httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return expectResultsCount(results, 1)
|
|
}
|
|
|
|
type workflowConditionUnmatch struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowConditionUnmatch) Execute(filePath string) error {
|
|
router := httprouter.New()
|
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "This is test matcher text")
|
|
})
|
|
ts := httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return expectResultsCount(results, 0)
|
|
}
|
|
|
|
type workflowMatcherName struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowMatcherName) Execute(filePath string) error {
|
|
router := httprouter.New()
|
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "This is test matcher text")
|
|
})
|
|
ts := httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return expectResultsCount(results, 1)
|
|
}
|
|
|
|
type workflowComplexConditions struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowComplexConditions) Execute(filePath string) error {
|
|
router := httprouter.New()
|
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "This is test matcher text")
|
|
})
|
|
ts := httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, result := range results {
|
|
if !strings.Contains(result, "test-matcher-3") {
|
|
return fmt.Errorf("incorrect result: the \"basic-get-third:test-matcher-3\" and only that should be matched!\nResults:\n\t%s", strings.Join(results, "\n\t"))
|
|
}
|
|
}
|
|
return expectResultsCount(results, 2)
|
|
}
|
|
|
|
type workflowHttpKeyValueShare struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowHttpKeyValueShare) Execute(filePath string) error {
|
|
router := httprouter.New()
|
|
router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "href=\"test-value\"")
|
|
})
|
|
router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
body, _ := io.ReadAll(r.Body)
|
|
_, _ = fmt.Fprintf(w, "%s", body)
|
|
})
|
|
ts := httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return expectResultsCount(results, 1)
|
|
}
|
|
|
|
type workflowDnsKeyValueShare struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowDnsKeyValueShare) Execute(filePath string) error {
|
|
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, "http://scanme.sh", debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// no results - ensure that the variable sharing works
|
|
return expectResultsCount(results, 1)
|
|
}
|
|
|
|
type workflowCodeKeyValueShare struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowCodeKeyValueShare) Execute(filePath string) error {
|
|
// provide the Certificate File that the code templates are signed with
|
|
certEnvVar := signer.CertEnvVarName + "=" + testCertFile
|
|
|
|
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, []string{certEnvVar}, "-workflows", filePath, "-target", "input", "-code")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return expectResultsCount(results, 1)
|
|
}
|
|
|
|
type workflowMultiProtocolKeyValueShare struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowMultiProtocolKeyValueShare) Execute(filePath string) error {
|
|
router := httprouter.New()
|
|
// the response of path1 contains a domain that will be extracted and shared with the second template
|
|
router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "href=\"blog.projectdiscovery.io\"")
|
|
})
|
|
// path2 responds with the value of the "extracted" query parameter, e.g.: /path2?extracted=blog.projectdiscovery.io => blog.projectdiscovery.io
|
|
router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "%s", r.URL.Query().Get("extracted"))
|
|
})
|
|
ts := httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return expectResultsCount(results, 2)
|
|
}
|
|
|
|
type workflowMultiMatchKeyValueShare struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowMultiMatchKeyValueShare) Execute(filePath string) error {
|
|
var receivedData []string
|
|
router := httprouter.New()
|
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "This is test matcher text")
|
|
})
|
|
router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
_, _ = fmt.Fprintf(w, "href=\"test-value-%s\"", r.URL.Query().Get("v"))
|
|
})
|
|
router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
|
body, _ := io.ReadAll(r.Body)
|
|
receivedData = append(receivedData, string(body))
|
|
_, _ = fmt.Fprintf(w, "test-value")
|
|
})
|
|
ts := httptest.NewServer(router)
|
|
defer ts.Close()
|
|
|
|
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if we received the data from both request to /path1 and it is not overwritten by the later one.
|
|
// They will appear in brackets because of another bug: https://github.com/orgs/projectdiscovery/discussions/3766
|
|
if !sliceutil.Contains(receivedData, "[test-value-1]") || !sliceutil.Contains(receivedData, "[test-value-2]") {
|
|
return fmt.Errorf(
|
|
"incorrect data: did not receive both extracted data from the first request!\nReceived Data:\n\t%s\nResults:\n\t%s",
|
|
strings.Join(receivedData, "\n\t"),
|
|
strings.Join(results, "\n\t"),
|
|
)
|
|
}
|
|
// The number of expected results is 3: the workflow's Matcher Name based condition check forwards both match, and the other branch with simple subtemplates goes with one
|
|
return expectResultsCount(results, 3)
|
|
}
|
|
|
|
type workflowSharedCookies struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *workflowSharedCookies) Execute(filePath string) error {
|
|
handleFunc := func(name string, w http.ResponseWriter, _ *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)
|
|
}
|