Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extending YAML to support include preprocessing #1767

Merged
merged 10 commits into from
Dec 13, 2022
3 changes: 1 addition & 2 deletions v2/pkg/parsers/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import (
"regexp"
"strings"

"gopkg.in/yaml.v2"

"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader/filter"
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
"github.com/projectdiscovery/nuclei/v2/pkg/templates/cache"
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
"github.com/projectdiscovery/nuclei/v2/pkg/utils/stats"
"gopkg.in/yaml.v2"
)

const (
Expand Down
32 changes: 17 additions & 15 deletions v2/pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strings"

"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
"github.com/projectdiscovery/nuclei/v2/pkg/utils/yaml"
fileutil "github.com/projectdiscovery/utils/file"
)

func IsBlank(value string) bool {
Expand All @@ -27,42 +29,42 @@ func UnwrapError(err error) error {

// IsURL tests a string to determine if it is a well-structured url or not.
func IsURL(input string) bool {
_, err := url.ParseRequestURI(input)
if err != nil {
return false
}

u, err := url.Parse(input)
if err != nil || u.Scheme == "" || u.Host == "" {
return false
}

return true
return err == nil && u.Scheme != "" && u.Host != ""
}

// ReadFromPathOrURL reads and returns the contents of a file or url.
func ReadFromPathOrURL(templatePath string, catalog catalog.Catalog) (data []byte, err error) {
var reader io.Reader
if IsURL(templatePath) {
resp, err := http.Get(templatePath)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
reader = resp.Body
} else {
f, err := catalog.OpenFile(templatePath)
if err != nil {
return nil, err
}
defer f.Close()
data, err = io.ReadAll(f)
reader = f
}

data, err = io.ReadAll(reader)
if err != nil {
return nil, err
}

// pre-process directives only for local files
if fileutil.FileExists(templatePath) {
data, err = yaml.PreProcess(data)
if err != nil {
return nil, err
}
}

return
}

Expand Down
65 changes: 65 additions & 0 deletions v2/pkg/utils/yaml/preprocess.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package yaml

import (
"bytes"
"os"
"regexp"
"strings"

fileutil "github.com/projectdiscovery/utils/file"
stringsutil "github.com/projectdiscovery/utils/strings"
)

var reImportsPattern = regexp.MustCompile(`(?m)# !include:(.+.yaml)`)

// PreProcess all include directives
func PreProcess(data []byte) ([]byte, error) {
// find all matches like !include:path\n
importMatches := reImportsPattern.FindAllSubmatch(data, -1)

var replaceItems []string

for _, match := range importMatches {
var (
matchString string
includeFileName string
)
matchBytes := match[0]
matchString = string(matchBytes)
if len(match) > 0 {
includeFileName = string(match[1])
}

// gets the number of tabs/spaces between the last \n and the beginning of the match
matchIndex := bytes.Index(data, matchBytes)
lastNewLineIndex := bytes.LastIndex(data[:matchIndex], []byte("\n"))
padBytes := data[lastNewLineIndex:matchIndex]

// check if the file exists
if fileutil.FileExists(includeFileName) {
// and in case replace the comment with it
includeFileContent, err := os.ReadFile(includeFileName)
if err != nil {
return nil, err
}
// if it's yaml, tries to preprocess that too recursively
if stringsutil.HasSuffixAny(includeFileName, ".yaml") {
if subIncludedFileContent, err := PreProcess(includeFileContent); err == nil {
includeFileContent = subIncludedFileContent
} else {
return nil, err
}
}

// pad each line of file content with padBytes
includeFileContent = bytes.ReplaceAll(includeFileContent, []byte("\n"), padBytes)

replaceItems = append(replaceItems, matchString)
replaceItems = append(replaceItems, string(includeFileContent))
}
}

replacer := strings.NewReplacer(replaceItems...)

return []byte(replacer.Replace(string(data))), nil
}