mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-10 17:37:57 -10:00
Template YAML decodes nested maps as `map[interface{}]interface{}`, which
the JSON encoder rejects, so a result whose `info.metadata` (or javascript
args carried in `meta`) held a map was dropped from `-j`/`-jsonl` output.
`model.Info` and `output.ResultEvent` now encode their metadata with
string-keyed maps.
Closes #7971
Co-Authored-By: Claude Opus 5.5 <[email protected]>
184 lines
5.7 KiB
Go
184 lines
5.7 KiB
Go
package model
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestInfoJsonMarshal(t *testing.T) {
|
|
info := Info{
|
|
Name: "Test Template Name",
|
|
Authors: stringslice.StringSlice{Value: []string{"forgedhallpass", "ice3man"}},
|
|
Description: "Test description",
|
|
SeverityHolder: severity.Holder{Severity: severity.High},
|
|
Tags: stringslice.StringSlice{Value: []string{"cve", "misc"}},
|
|
Reference: stringslice.NewRawStringSlice("Reference1"),
|
|
Metadata: map[string]interface{}{
|
|
"string_key": "string_value",
|
|
"array_key": []string{"array_value1", "array_value2"},
|
|
"map_key": map[string]string{
|
|
"key1": "val1",
|
|
},
|
|
},
|
|
}
|
|
|
|
result, err := json.Marshal(&info)
|
|
require.Nil(t, err)
|
|
|
|
expected := `{"name":"Test Template Name","author":["forgedhallpass","ice3man"],"tags":["cve","misc"],"description":"Test description","reference":"Reference1","severity":"high","metadata":{"array_key":["array_value1","array_value2"],"map_key":{"key1":"val1"},"string_key":"string_value"}}`
|
|
require.Equal(t, expected, string(result))
|
|
}
|
|
|
|
func TestInfoJsonMarshalNestedYAMLMetadata(t *testing.T) {
|
|
var info Info
|
|
err := yaml.Unmarshal([]byte(`
|
|
name: Nested metadata
|
|
author: pdteam
|
|
tags: misc
|
|
severity: info
|
|
metadata:
|
|
labels:
|
|
en: Hello
|
|
1: one
|
|
items:
|
|
- name: first
|
|
tags:
|
|
fr: Bonjour
|
|
plain: value
|
|
`), &info)
|
|
require.NoError(t, err)
|
|
require.IsType(t, map[interface{}]interface{}{}, info.Metadata["labels"])
|
|
|
|
for _, value := range []interface{}{info, &info} {
|
|
result, err := json.Marshal(value)
|
|
require.NoError(t, err)
|
|
require.JSONEq(t, `{"name":"Nested metadata","author":["pdteam"],"tags":["misc"],"severity":"info","metadata":{"labels":{"en":"Hello","1":"one"},"items":[{"name":"first","tags":{"fr":"Bonjour"}}],"plain":"value"}}`, string(result))
|
|
}
|
|
require.IsType(t, map[interface{}]interface{}{}, info.Metadata["labels"], "marshaling must not modify the info")
|
|
}
|
|
|
|
func TestInfoYamlMarshal(t *testing.T) {
|
|
info := Info{
|
|
Name: "Test Template Name",
|
|
Authors: stringslice.StringSlice{Value: []string{"forgedhallpass", "ice3man"}},
|
|
Description: "Test description",
|
|
SeverityHolder: severity.Holder{Severity: severity.High},
|
|
Tags: stringslice.StringSlice{Value: []string{"cve", "misc"}},
|
|
Reference: stringslice.NewRawStringSlice("Reference1"),
|
|
Metadata: map[string]interface{}{
|
|
"string_key": "string_value",
|
|
"array_key": []string{"array_value1", "array_value2"},
|
|
"map_key": map[string]string{
|
|
"key1": "val1",
|
|
},
|
|
},
|
|
}
|
|
|
|
result, err := yaml.Marshal(&info)
|
|
require.Nil(t, err)
|
|
|
|
expected := `name: Test Template Name
|
|
author:
|
|
- forgedhallpass
|
|
- ice3man
|
|
tags:
|
|
- cve
|
|
- misc
|
|
description: Test description
|
|
reference: Reference1
|
|
severity: high
|
|
metadata:
|
|
array_key:
|
|
- array_value1
|
|
- array_value2
|
|
map_key:
|
|
key1: val1
|
|
string_key: string_value
|
|
`
|
|
require.Equal(t, expected, string(result))
|
|
}
|
|
|
|
func TestUnmarshal(t *testing.T) {
|
|
templateName := "Test Template"
|
|
authors := []string{"forgedhallpass", "ice3man"}
|
|
tags := []string{"cve", "misc"}
|
|
references := []string{"http://test.com", "http://Domain.com"}
|
|
|
|
dynamicKey1 := "customDynamicKey1"
|
|
dynamicKey2 := "customDynamicKey2"
|
|
|
|
dynamicKeysMap := map[string]interface{}{
|
|
dynamicKey1: "customDynamicValue1",
|
|
dynamicKey2: "customDynamicValue2",
|
|
}
|
|
|
|
assertUnmarshalledTemplateInfo := func(t *testing.T, yamlPayload string) Info {
|
|
t.Helper()
|
|
info := Info{}
|
|
err := yaml.Unmarshal([]byte(yamlPayload), &info)
|
|
require.Nil(t, err)
|
|
require.Equal(t, info.Name, templateName)
|
|
require.Equal(t, info.Authors.ToSlice(), authors)
|
|
require.Equal(t, info.Tags.ToSlice(), tags)
|
|
require.Equal(t, info.SeverityHolder.Severity, severity.Critical)
|
|
require.Equal(t, info.Reference.ToSlice(), references)
|
|
require.Equal(t, info.Metadata, dynamicKeysMap)
|
|
return info
|
|
}
|
|
|
|
yamlPayload1 := `
|
|
name: ` + templateName + `
|
|
author: ` + strings.Join(authors, ", ") + `
|
|
tags: ` + strings.Join(tags, ", ") + `
|
|
severity: critical
|
|
reference: ` + strings.Join(references, ",") + `
|
|
metadata:
|
|
` + dynamicKey1 + `: ` + dynamicKeysMap[dynamicKey1].(string) + `
|
|
` + dynamicKey2 + `: ` + dynamicKeysMap[dynamicKey2].(string) + `
|
|
`
|
|
yamlPayload2 := `
|
|
name: ` + templateName + `
|
|
author:
|
|
- ` + authors[0] + `
|
|
- ` + authors[1] + `
|
|
tags:
|
|
- ` + tags[0] + `
|
|
- ` + tags[1] + `
|
|
severity: critical
|
|
reference:
|
|
- ` + references[0] + ` # comments are not unmarshalled
|
|
- ` + references[1] + `
|
|
metadata:
|
|
` + dynamicKey1 + `: ` + dynamicKeysMap[dynamicKey1].(string) + `
|
|
` + dynamicKey2 + `: ` + dynamicKeysMap[dynamicKey2].(string) + `
|
|
`
|
|
|
|
info1 := assertUnmarshalledTemplateInfo(t, yamlPayload1)
|
|
info2 := assertUnmarshalledTemplateInfo(t, yamlPayload2)
|
|
require.Equal(t, info1, info2)
|
|
}
|
|
|
|
func TestInfoFilledFields(t *testing.T) {
|
|
written := Info{Description: "{{host}} is exposed", Impact: "Data loss"}
|
|
filled := written
|
|
filled.Description = "example.com is exposed"
|
|
|
|
require.Nil(t, filled.FilledFields(nil))
|
|
fields := filled.FilledFields(&written)
|
|
require.Len(t, fields, 1)
|
|
require.Equal(t, "Description", fields[0].Label)
|
|
require.Equal(t, "example.com is exposed", *fields[0].Value)
|
|
}
|
|
|
|
func TestInfoHasPlaceholders(t *testing.T) {
|
|
require.False(t, (&Info{Description: "Redis is exposed"}).HasPlaceholders())
|
|
require.True(t, (&Info{Impact: "{{host}} is exposed"}).HasPlaceholders())
|
|
require.True(t, (&Info{Remediation: "rotate §password§"}).HasPlaceholders())
|
|
}
|