mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-18 04:05:27 +00:00
fix template signing singnature issue (#5869)
* fix incorrect .gitignore * template signer utility tool * use yaml marhsal & unmarshal for normalization * normalize before verification
This commit is contained in:
parent
557b4fba38
commit
16735f5243
38
.gitignore
vendored
38
.gitignore
vendored
@ -2,8 +2,8 @@
|
|||||||
**/*-cache
|
**/*-cache
|
||||||
**/*-config
|
**/*-config
|
||||||
**/.cache
|
**/.cache
|
||||||
*.DS_Store
|
**/*.DS_Store
|
||||||
*.exe
|
**/*.exe
|
||||||
.devcontainer
|
.devcontainer
|
||||||
.gitignore
|
.gitignore
|
||||||
.idea
|
.idea
|
||||||
@ -11,23 +11,23 @@
|
|||||||
|
|
||||||
# Binaries
|
# Binaries
|
||||||
/bin/*
|
/bin/*
|
||||||
**/bindgen
|
/bindgen
|
||||||
**/debug-*
|
/debug-*
|
||||||
**/docgen
|
/docgen
|
||||||
**/functional-test
|
/functional-test
|
||||||
**/fuzzplayground
|
/fuzzplayground
|
||||||
**/integration-test
|
/integration-test
|
||||||
**/jsdocgen
|
/jsdocgen
|
||||||
**/main
|
/main
|
||||||
**/memogen
|
/memogen
|
||||||
**/nuclei
|
/nuclei
|
||||||
**/nuclei-stats*
|
/nuclei-stats*
|
||||||
**/nuclei_dev
|
/nuclei_dev
|
||||||
**/nuclei_main
|
/nuclei_main
|
||||||
**/scan-charts
|
/scan-charts
|
||||||
**/scrapefunc
|
/scrapefunc
|
||||||
**/scrapefuncs
|
/scrapefuncs
|
||||||
**/tsgen
|
/tsgen
|
||||||
|
|
||||||
# Templates
|
# Templates
|
||||||
/*.yaml
|
/*.yaml
|
||||||
|
|||||||
4
Makefile
4
Makefile
@ -42,6 +42,10 @@ scan-charts: GOBUILD_OUTPUT = ./bin/scan-charts
|
|||||||
scan-charts: GOBUILD_PACKAGES = cmd/scan-charts/main.go
|
scan-charts: GOBUILD_PACKAGES = cmd/scan-charts/main.go
|
||||||
scan-charts: go-build
|
scan-charts: go-build
|
||||||
|
|
||||||
|
template-signer: GOBUILD_OUTPUT = ./bin/template-signer
|
||||||
|
template-signer: GOBUILD_PACKAGES = cmd/tools/signer/main.go
|
||||||
|
template-signer: go-build
|
||||||
|
|
||||||
docgen: GOBUILD_OUTPUT = ./bin/docgen
|
docgen: GOBUILD_OUTPUT = ./bin/docgen
|
||||||
docgen: GOBUILD_PACKAGES = cmd/docgen/docgen.go
|
docgen: GOBUILD_PACKAGES = cmd/docgen/docgen.go
|
||||||
docgen: bin = dstdocgen
|
docgen: bin = dstdocgen
|
||||||
|
|||||||
114
cmd/tools/signer/main.go
Normal file
114
cmd/tools/signer/main.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/projectdiscovery/gologger"
|
||||||
|
"github.com/projectdiscovery/gologger/levels"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||||
|
fileutil "github.com/projectdiscovery/utils/file"
|
||||||
|
folderutil "github.com/projectdiscovery/utils/folder"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
appConfigDir = folderutil.AppConfigDirOrDefault(".config", "nuclei")
|
||||||
|
defaultCertFile = filepath.Join(appConfigDir, "keys", "nuclei-user.crt")
|
||||||
|
defaultPrivKey = filepath.Join(appConfigDir, "keys", "nuclei-user-private-key.pem")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
template string
|
||||||
|
cert string
|
||||||
|
privKey string
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.StringVar(&template, "template", "", "template to sign (file only)")
|
||||||
|
flag.StringVar(&cert, "cert", defaultCertFile, "certificate file")
|
||||||
|
flag.StringVar(&privKey, "priv-key", defaultPrivKey, "private key file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
config.DefaultConfig.LogAllEvents = true
|
||||||
|
gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
|
||||||
|
|
||||||
|
if template == "" {
|
||||||
|
gologger.Fatal().Msg("template is required")
|
||||||
|
}
|
||||||
|
if !fileutil.FileExists(template) {
|
||||||
|
gologger.Fatal().Msgf("template file %s does not exist or not a file", template)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get signer
|
||||||
|
tmplSigner, err := signer.NewTemplateSignerFromFiles(cert, privKey)
|
||||||
|
if err != nil {
|
||||||
|
gologger.Fatal().Msgf("failed to create signer: %s", err)
|
||||||
|
}
|
||||||
|
gologger.Info().Msgf("Template Signer: %v\n", tmplSigner.Identifier())
|
||||||
|
|
||||||
|
// read file
|
||||||
|
bin, err := os.ReadFile(template)
|
||||||
|
if err != nil {
|
||||||
|
gologger.Fatal().Msgf("failed to read template file %s: %s", template, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract signature and content
|
||||||
|
sig, content := signer.ExtractSignatureAndContent(bin)
|
||||||
|
hash := sha256.Sum256(content)
|
||||||
|
|
||||||
|
gologger.Info().Msgf("Signature Details:")
|
||||||
|
gologger.Info().Msgf("----------------")
|
||||||
|
gologger.Info().Msgf("Signature: %s", sig)
|
||||||
|
gologger.Info().Msgf("Content Hash (SHA256): %s\n", hex.EncodeToString(hash[:]))
|
||||||
|
|
||||||
|
execOpts := defaultExecutorOpts(template)
|
||||||
|
|
||||||
|
tmpl, err := templates.Parse(template, nil, execOpts)
|
||||||
|
if err != nil {
|
||||||
|
gologger.Fatal().Msgf("failed to parse template: %s", err)
|
||||||
|
}
|
||||||
|
gologger.Info().Msgf("Template Verified: %v\n", tmpl.Verified)
|
||||||
|
|
||||||
|
if !tmpl.Verified {
|
||||||
|
gologger.Info().Msgf("------------------------")
|
||||||
|
gologger.Info().Msg("Template is not verified, signing template")
|
||||||
|
if err := templates.SignTemplate(tmplSigner, template); err != nil {
|
||||||
|
gologger.Fatal().Msgf("Failed to sign template: %s", err)
|
||||||
|
}
|
||||||
|
// verify again by reading file what the new signature and hash is
|
||||||
|
bin2, err := os.ReadFile(template)
|
||||||
|
if err != nil {
|
||||||
|
gologger.Fatal().Msgf("failed to read signed template file %s: %s", template, err)
|
||||||
|
}
|
||||||
|
sig2, content2 := signer.ExtractSignatureAndContent(bin2)
|
||||||
|
hash2 := sha256.Sum256(content2)
|
||||||
|
|
||||||
|
gologger.Info().Msgf("Updated Signature Details:")
|
||||||
|
gologger.Info().Msgf("------------------------")
|
||||||
|
gologger.Info().Msgf("Signature: %s", sig2)
|
||||||
|
gologger.Info().Msgf("Content Hash (SHA256): %s\n", hex.EncodeToString(hash2[:]))
|
||||||
|
}
|
||||||
|
gologger.Info().Msgf("✓ Template signed & verified successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultExecutorOpts(templatePath string) protocols.ExecutorOptions {
|
||||||
|
// use parsed options when initializing signer instead of default options
|
||||||
|
options := types.DefaultOptions()
|
||||||
|
templates.UseOptionsForSigner(options)
|
||||||
|
catalog := disk.NewCatalog(filepath.Dir(templatePath))
|
||||||
|
executerOpts := protocols.ExecutorOptions{
|
||||||
|
Catalog: catalog,
|
||||||
|
Options: options,
|
||||||
|
TemplatePath: templatePath,
|
||||||
|
Parser: templates.NewParser(),
|
||||||
|
}
|
||||||
|
return executerOpts
|
||||||
|
}
|
||||||
@ -451,6 +451,9 @@ func parseTemplate(data []byte, options protocols.ExecutorOptions) (*Template, e
|
|||||||
var verifier *signer.TemplateSigner
|
var verifier *signer.TemplateSigner
|
||||||
for _, verifier = range signer.DefaultTemplateVerifiers {
|
for _, verifier = range signer.DefaultTemplateVerifiers {
|
||||||
template.Verified, _ = verifier.Verify(data, template)
|
template.Verified, _ = verifier.Verify(data, template)
|
||||||
|
if config.DefaultConfig.LogAllEvents {
|
||||||
|
gologger.Verbose().Msgf("template %v verified by %s : %v", template.ID, verifier.Identifier(), template.Verified)
|
||||||
|
}
|
||||||
if template.Verified {
|
if template.Verified {
|
||||||
template.TemplateVerifier = verifier.Identifier()
|
template.TemplateVerifier = verifier.Identifier()
|
||||||
break
|
break
|
||||||
|
|||||||
@ -30,11 +30,12 @@ func ExtractSignatureAndContent(data []byte) (signature, content []byte) {
|
|||||||
dataStr := string(data)
|
dataStr := string(data)
|
||||||
if idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 {
|
if idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 {
|
||||||
signature = []byte(strings.TrimSpace(dataStr[idx:]))
|
signature = []byte(strings.TrimSpace(dataStr[idx:]))
|
||||||
content = []byte(strings.TrimSpace(dataStr[:idx]))
|
content = bytes.TrimSpace(data[:idx])
|
||||||
} else {
|
} else {
|
||||||
content = data
|
content = data
|
||||||
}
|
}
|
||||||
return
|
content = bytes.TrimSpace(content)
|
||||||
|
return signature, content
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignableTemplate is a template that can be signed
|
// SignableTemplate is a template that can be signed
|
||||||
@ -145,6 +146,10 @@ func (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// normalize content by removing \r\n everywhere since this only done for verification
|
||||||
|
// it does not affect the actual template
|
||||||
|
content = bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n"))
|
||||||
|
|
||||||
buff := bytes.NewBuffer(content)
|
buff := bytes.NewBuffer(content)
|
||||||
// if file has any imports process them
|
// if file has any imports process them
|
||||||
for _, file := range tmpl.GetFileImports() {
|
for _, file := range tmpl.GetFileImports() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user