Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions internal/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -953,8 +953,10 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) {
r.Logger.Warning().Msg("No DAST templates found")
}
stats.ForceDisplayWarning(templates.SkippedUnverifiedCodeTemplateStats)
stats.ForceDisplayWarning(templates.SkippedUnverifiedJavascriptTemplateStats)
} else {
stats.DisplayAsWarning(templates.SkippedUnverifiedCodeTemplateStats)
stats.DisplayAsWarning(templates.SkippedUnverifiedJavascriptTemplateStats)
}

stats.DisplayAsWarning(httpProtocol.SetThreadToCountZero)
Expand Down
49 changes: 32 additions & 17 deletions internal/tests/integration/code_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (
)

var isCodeDisabled = func() bool { return osutils.IsWindows() && os.Getenv("CI") == "true" }
var signedCodeTemplates sync.Once
var signedCodeTemplatesErr error
var signedIntegrationTemplates sync.Once
var signedIntegrationTemplatesErr error

func hasAnyExecutable(names ...string) bool {
for _, name := range names {
Expand Down Expand Up @@ -51,23 +51,20 @@ const (

var testcertpath = ""

func ensureSignedCodeTemplates() error {
if isCodeDisabled() {
return nil
}
signedCodeTemplates.Do(func() {
func ensureSignedIntegrationTemplates() error {
signedIntegrationTemplates.Do(func() {
previousWD, err := os.Getwd()
if err != nil {
signedCodeTemplatesErr = err
signedIntegrationTemplatesErr = err
return
}
if err := os.Chdir(suite.fixturesDir); err != nil {
signedCodeTemplatesErr = err
signedIntegrationTemplatesErr = err
return
}
defer func() {
if chdirErr := os.Chdir(previousWD); chdirErr != nil && signedCodeTemplatesErr == nil {
signedCodeTemplatesErr = chdirErr
if chdirErr := os.Chdir(previousWD); chdirErr != nil && signedIntegrationTemplatesErr == nil {
signedIntegrationTemplatesErr = chdirErr
}
}()

Expand All @@ -79,13 +76,16 @@ func ensureSignedCodeTemplates() error {

tsigner, err := signer.NewTemplateSignerFromFiles(certPath, keyPath)
if err != nil {
signedCodeTemplatesErr = err
signedIntegrationTemplatesErr = err
return
}

templatesToSign := []string{
"workflow/code-template-1.yaml",
"workflow/code-template-2.yaml",
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() {
Expand All @@ -102,14 +102,20 @@ func ensureSignedCodeTemplates() error {
}
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 {
signedCodeTemplatesErr = err
signedIntegrationTemplatesErr = err
return
}
}
})
return signedCodeTemplatesErr
return signedIntegrationTemplatesErr
}

func getEnvValues() []string {
Expand All @@ -118,6 +124,15 @@ func getEnvValues() []string {
}
}

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
Expand Down
4 changes: 1 addition & 3 deletions internal/tests/integration/javascript_krbroast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import (
"github.com/jcmturner/gokrb5/v8/iana/nametype"
"github.com/jcmturner/gokrb5/v8/messages"
"github.com/jcmturner/gokrb5/v8/types"

"github.com/projectdiscovery/nuclei/v3/internal/tests/testutils"
)

// javascriptASRepRoast exercises templates/ad/asrep-roast.yaml end-to-end
Expand All @@ -46,7 +44,7 @@ func (j *javascriptASRepRoast) Execute(filePath string) error {
}
defer kdc.Close()

results, err := testutils.RunNucleiTemplateAndGetResults(filePath, kdc.Address(), debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, kdc.Address(), debug)
if err != nil {
return err
}
Expand Down
18 changes: 9 additions & 9 deletions internal/tests/integration/javascript_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func newJavascriptDockerSpec(port string, options *dockertest.RunOptions, readyT
type javascriptNetHttps struct{}

func (j *javascriptNetHttps) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
Expand Down Expand Up @@ -167,7 +167,7 @@ type javascriptMultiPortsSSH struct{}

func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
// use scanme.sh as target to ensure we match on the 2nd default port 22
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
Expand All @@ -177,7 +177,7 @@ func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
type javascriptNoPortArgs struct{}

func (j *javascriptNoPortArgs) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "yo.dawg", debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, "yo.dawg", debug)
if err != nil {
return err
}
Expand All @@ -192,7 +192,7 @@ func (j *javascriptWMICommand) Execute(filePath string) error {
// the exclude list and short-circuits before any dial, so the JSON result
// must contain "ok":false plus "network policy" in the error while not
// leaking the password.
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug, "-eh", "203.0.113.10")
if err != nil {
return err
}
Expand All @@ -204,7 +204,7 @@ type javascriptGoExecRedaction struct{}
func (j *javascriptGoExecRedaction) Execute(filePath string) error {
listener := newGoExecCloseListener()
defer listener.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
results, err := runSignedNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
if err != nil {
return err
}
Expand All @@ -216,7 +216,7 @@ type javascriptGoExecModules struct{}
func (j *javascriptGoExecModules) Execute(filePath string) error {
listener := newGoExecCloseListener()
defer listener.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
results, err := runSignedNucleiTemplateAndGetResults(filePath, listener.host, debug, "-V", "RPCEndpoint="+listener.binding)
if err != nil {
return err
}
Expand Down Expand Up @@ -275,7 +275,7 @@ func (j *javascriptGoExecSambaNTLM) Execute(filePath string) error {

errS := make([]error, 0, defaultRetry)
for attempt := 1; attempt <= defaultRetry; attempt++ {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, "127.0.0.1", debug)
if err == nil {
if countErr := expectResultsCount(results, 1); countErr == nil {
return nil
Expand Down Expand Up @@ -383,7 +383,7 @@ func (j *networkMultiStep) Execute(filePath string) error {
})
defer server.Close()

results, err := testutils.RunNucleiTemplateAndGetResults(filePath, server.URL, debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, server.URL, debug)
if err != nil {
return err
}
Expand Down Expand Up @@ -439,7 +439,7 @@ func runJavascriptDockerCase(filePath string, spec javascriptDockerSpec, expecte

errS := make([]error, 0, defaultRetry)
for attempt := 1; attempt <= defaultRetry; attempt++ {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, targetAddress, debug)
results, err := runSignedNucleiTemplateAndGetResults(filePath, targetAddress, debug)
if err == nil {
if countErr := expectResultsCount(results, expectedNumbers...); countErr == nil {
return nil
Expand Down
2 changes: 1 addition & 1 deletion internal/tests/integration/matcher-status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ type javascriptNoAccess struct{}

// Execute executes a test case and returns an error if occurred
func (h *javascriptNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
results, err := runSignedNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
Expand Down
12 changes: 7 additions & 5 deletions internal/tests/integration/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,9 @@ func executeIntegrationCase(testCase integrationCase) error {
if testCase.DisableOn != nil && testCase.DisableOn() {
return nil
}
if needsSignedCodeTemplates(testCase.Path) {
if err := ensureSignedCodeTemplates(); err != nil {
return fmt.Errorf("failed to sign code templates: %w", err)
if needsSignedIntegrationTemplates(testCase.Path) {
if err := ensureSignedIntegrationTemplates(); err != nil {
return fmt.Errorf("failed to sign integration templates: %w", err)
}
}

Expand Down Expand Up @@ -191,8 +191,10 @@ func parallelism() int {
return parallelism
}

func needsSignedCodeTemplates(path string) bool {
return strings.HasPrefix(path, "protocols/code/") || strings.Contains(path, "workflow/code-")
func needsSignedIntegrationTemplates(path string) bool {
return strings.HasPrefix(path, "protocols/code/") ||
strings.Contains(path, "workflow/code-") ||
strings.HasPrefix(path, "protocols/javascript/")
}

func testNameForPath(path string) string {
Expand Down
10 changes: 10 additions & 0 deletions pkg/catalog/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,16 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*temp
return
}

// javascript-protocol templates expose Go-backed modules through
// the JS runtime, so unsigned ones are rejected before execution.
if parsed.IsUnsignedJavascriptTemplate() {
stats.Increment(templates.SkippedUnverifiedJavascriptTemplateStats)
if config.DefaultConfig.LogAllEvents {
store.logger.Warning().Msgf("Unverified javascript template at %q", templatePath)
}
return
}

if missingCaps := parsed.MissingLoadCapabilities(caps); len(missingCaps) > 0 {
store.noteMissingCapabilities(templatePath, missingCaps)
return
Expand Down
116 changes: 116 additions & 0 deletions pkg/catalog/loader/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,122 @@ code:
require.Equal(t, initialUnverified, stats.GetValue(templates.SkippedUnverifiedTemplateStats))
}

func loadSingleTemplateForTest(t *testing.T, templatePath, executionID string) []*templates.Template {
t.Helper()

options := testutils.DefaultOptions.Copy()
options.Logger = &gologger.Logger{}
options.ExecutionId = executionID
options.DisableUnsignedTemplates = false
options.TemplateLoadingConcurrency = 1
testutils.Init(options)
t.Cleanup(func() {
testutils.Cleanup(options)
})

catalog := disk.NewCatalog("")
executerOpts := testutils.NewMockExecuterOptions(options, nil)
executerOpts.Catalog = catalog
executerOpts.Parser = templates.NewParser()
executerOpts.Logger = options.Logger

workflowLoader, err := workflow.NewLoader(executerOpts)
require.NoError(t, err)
executerOpts.WorkflowLoader = workflowLoader

store, err := New(NewConfig(options, catalog, executerOpts))
require.NoError(t, err)

loaded, err := store.LoadTemplates([]string{templatePath})
require.NoError(t, err)
return loaded
}

func TestLoadTemplatesRecordsUnsignedJavascriptTemplateOnlyAsJavascriptSkip(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "unsigned-javascript.yaml")
err := os.WriteFile(templatePath, []byte(`id: unsigned-javascript-template

info:
name: Unsigned Javascript Template
author: pdteam
severity: info

javascript:
- code: |
Export("unsigned-javascript-template")
`), 0o600)
require.NoError(t, err)

initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)
initialUnverified := stats.GetValue(templates.SkippedUnverifiedTemplateStats)

loaded := loadSingleTemplateForTest(t, templatePath, "loader-unsigned-javascript-template")
require.Empty(t, loaded)
require.Equal(t, initialUnverifiedJavascript+1, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
require.Equal(t, initialUnverified, stats.GetValue(templates.SkippedUnverifiedTemplateStats))
}

func TestLoadTemplatesTreatsMixedTemplateWithJavascriptAsJavascriptSensitive(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "mixed-javascript.yaml")
err := os.WriteFile(templatePath, []byte(`id: mixed-javascript-template

info:
name: Mixed Javascript Template
author: pdteam
severity: info

http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- mixed-javascript-template

javascript:
- code: |
Export("mixed-javascript-template")
`), 0o600)
require.NoError(t, err)

initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)

loaded := loadSingleTemplateForTest(t, templatePath, "loader-mixed-javascript-template")
require.Empty(t, loaded)
require.Equal(t, initialUnverifiedJavascript+1, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
}

func TestLoadTemplatesAllowsUnsignedFlowTemplateWithoutJavascriptProtocol(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "flow-only.yaml")
err := os.WriteFile(templatePath, []byte(`id: unsigned-flow-template

info:
name: Unsigned Flow Template
author: pdteam
severity: info

flow: http(1)

http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- unsigned-flow-template
`), 0o600)
require.NoError(t, err)

initialUnverifiedJavascript := stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats)

loaded := loadSingleTemplateForTest(t, templatePath, "loader-unsigned-flow-template")
require.Len(t, loaded, 1)
require.Equal(t, "unsigned-flow-template", loaded[0].ID)
require.Equal(t, initialUnverifiedJavascript, stats.GetValue(templates.SkippedUnverifiedJavascriptTemplateStats))
}

func TestLoadTemplatesDoesNotRequireGlobalMatchersFlagToLoadTemplate(t *testing.T) {
templatePath := filepath.Join(t.TempDir(), "global-matchers.yaml")
err := os.WriteFile(templatePath, []byte(`id: global-matchers-template
Expand Down
Loading
Loading