mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-10 17:37:57 -10:00
* feat(templates): require signatures for javascript templates Unsigned javascript templates should not be accepted into execution paths when template signing is enforced. Apply the signature check to javascript protocol templates in both direct loading and workflows, and report those skips separately from code templates. Signed-off-by: Dwi Siswanto <[email protected]> * fix(signer): normalize line endings when signing The verifier already canonicalizes CRLF to LF before checking the digest, but the signer still hashed the raw file content. Templates signed from a CRLF checkout could fail verification later, which showed up on Windows after javascript fixtures started being signed in the integration harness. Use the same canonical content for signing and verification, and add a CRLF sign-and-verify regression. Signed-off-by: Dwi Siswanto <[email protected]> --------- Signed-off-by: Dwi Siswanto <[email protected]>
230 lines
7.3 KiB
Go
230 lines
7.3 KiB
Go
//go:build integration
|
|
// +build integration
|
|
|
|
package integration_test
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"os/exec"
|
|
"sync"
|
|
|
|
osutils "github.com/projectdiscovery/utils/os"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
|
|
)
|
|
|
|
var isCodeDisabled = func() bool { return osutils.IsWindows() && os.Getenv("CI") == "true" }
|
|
var signedIntegrationTemplates sync.Once
|
|
var signedIntegrationTemplatesErr error
|
|
|
|
func hasAnyExecutable(names ...string) bool {
|
|
for _, name := range names {
|
|
if _, err := exec.LookPath(name); err == nil {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var codeTestCases = []integrationCase{
|
|
{Path: "protocols/code/py-snippet.yaml", TestCase: &codeSnippet{}, DisableOn: isCodeDisabled},
|
|
{Path: "protocols/code/py-file.yaml", TestCase: &codeFile{}, DisableOn: isCodeDisabled},
|
|
{Path: "protocols/code/py-env-var.yaml", TestCase: &codeEnvVar{}, DisableOn: isCodeDisabled},
|
|
{Path: "protocols/code/unsigned.yaml", TestCase: &unsignedCode{}, DisableOn: isCodeDisabled},
|
|
{Path: "protocols/code/dast-unsigned.yaml", TestCase: &dastUnsignedCode{}, DisableOn: isCodeDisabled},
|
|
{Path: "protocols/code/py-nosig.yaml", TestCase: &codePyNoSig{}, DisableOn: isCodeDisabled},
|
|
{Path: "protocols/code/py-interactsh.yaml", TestCase: &codeSnippet{}, DisableOn: isCodeDisabled},
|
|
{Path: "protocols/code/ps1-snippet.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsWindows() || isCodeDisabled() }},
|
|
{Path: "protocols/code/pre-condition.yaml", TestCase: &codePreCondition{}, DisableOn: isCodeDisabled},
|
|
{Path: "protocols/code/sh-virtual.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsLinux() || isCodeDisabled() || !hasAnyExecutable("docker", "podman") }, Serial: true},
|
|
{Path: "protocols/code/py-virtual.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsLinux() || isCodeDisabled() || !hasAnyExecutable("docker", "podman") }, Serial: true},
|
|
{Path: "protocols/code/pwsh-echo.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return isCodeDisabled() || !hasAnyExecutable("pwsh", "powershell", "powershell.exe") }},
|
|
}
|
|
|
|
const (
|
|
testCertFile = "protocols/keys/ci.crt"
|
|
testKeyFile = "protocols/keys/ci-private-key.pem"
|
|
)
|
|
|
|
var testcertpath = ""
|
|
|
|
func ensureSignedIntegrationTemplates() error {
|
|
signedIntegrationTemplates.Do(func() {
|
|
previousWD, err := os.Getwd()
|
|
if err != nil {
|
|
signedIntegrationTemplatesErr = err
|
|
return
|
|
}
|
|
if err := os.Chdir(suite.fixturesDir); err != nil {
|
|
signedIntegrationTemplatesErr = err
|
|
return
|
|
}
|
|
defer func() {
|
|
if chdirErr := os.Chdir(previousWD); chdirErr != nil && signedIntegrationTemplatesErr == nil {
|
|
signedIntegrationTemplatesErr = chdirErr
|
|
}
|
|
}()
|
|
|
|
templates.TemplateSignerLFA()
|
|
|
|
certPath := fixturePath(testCertFile)
|
|
keyPath := fixturePath(testKeyFile)
|
|
testcertpath = certPath
|
|
|
|
tsigner, err := signer.NewTemplateSignerFromFiles(certPath, keyPath)
|
|
if err != nil {
|
|
signedIntegrationTemplatesErr = err
|
|
return
|
|
}
|
|
|
|
var templatesToSign []string
|
|
if !isCodeDisabled() {
|
|
templatesToSign = append(templatesToSign,
|
|
"workflow/code-template-1.yaml",
|
|
"workflow/code-template-2.yaml",
|
|
)
|
|
}
|
|
for _, v := range codeTestCases {
|
|
if v.DisableOn != nil && v.DisableOn() {
|
|
continue
|
|
}
|
|
if _, ok := v.TestCase.(*unsignedCode); ok {
|
|
continue
|
|
}
|
|
if _, ok := v.TestCase.(*codePyNoSig); ok {
|
|
continue
|
|
}
|
|
if _, ok := v.TestCase.(*dastUnsignedCode); ok {
|
|
continue
|
|
}
|
|
templatesToSign = append(templatesToSign, v.Path)
|
|
}
|
|
for _, v := range jsTestcases {
|
|
if v.DisableOn != nil && v.DisableOn() {
|
|
continue
|
|
}
|
|
templatesToSign = append(templatesToSign, v.Path)
|
|
}
|
|
for _, templatePath := range templatesToSign {
|
|
if err := templates.SignTemplate(tsigner, fixturePath(templatePath)); err != nil {
|
|
signedIntegrationTemplatesErr = err
|
|
return
|
|
}
|
|
}
|
|
})
|
|
return signedIntegrationTemplatesErr
|
|
}
|
|
|
|
func getEnvValues() []string {
|
|
return []string{
|
|
signer.CertEnvVarName + "=" + testcertpath,
|
|
}
|
|
}
|
|
|
|
func runSignedNucleiTemplateAndGetResults(template, url string, debug bool, extra ...string) ([]string, error) {
|
|
if err := ensureSignedIntegrationTemplates(); err != nil {
|
|
return nil, err
|
|
}
|
|
args := []string{"-t", template, "-target", url}
|
|
args = append(args, extra...)
|
|
return testutils.RunNucleiBareArgsAndGetResults(debug, getEnvValues(), args...)
|
|
}
|
|
|
|
type codeSnippet struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *codeSnippet) Execute(filePath string) error {
|
|
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-code")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return expectResultsCount(results, 1)
|
|
}
|
|
|
|
type codePreCondition struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *codePreCondition) Execute(filePath string) error {
|
|
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-code", "-esc")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if osutils.IsLinux() {
|
|
return expectResultsCount(results, 1)
|
|
} else {
|
|
return expectResultsCount(results, 0)
|
|
|
|
}
|
|
}
|
|
|
|
type codeFile struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *codeFile) Execute(filePath string) error {
|
|
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-code")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return expectResultsCount(results, 1)
|
|
}
|
|
|
|
type codeEnvVar struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *codeEnvVar) Execute(filePath string) error {
|
|
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-V", "baz=baz", "-code")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return expectResultsCount(results, 1)
|
|
}
|
|
|
|
type unsignedCode struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *unsignedCode) Execute(filePath string) error {
|
|
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-code")
|
|
|
|
// should error out
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// this point should never be reached
|
|
return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed"))
|
|
}
|
|
|
|
type dastUnsignedCode struct{}
|
|
|
|
// Execute runs an unsigned template that pairs a fuzzable request with a code
|
|
// block under -dast, which must not bypass the code-signing check.
|
|
func (h *dastUnsignedCode) Execute(filePath string) error {
|
|
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-dast")
|
|
|
|
// should error out
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// this point should never be reached
|
|
return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed"))
|
|
}
|
|
|
|
type codePyNoSig struct{}
|
|
|
|
// Execute executes a test case and returns an error if occurred
|
|
func (h *codePyNoSig) Execute(filePath string) error {
|
|
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-code")
|
|
|
|
// should error out
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// this point should never be reached
|
|
return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed"))
|
|
}
|