From 17f22855ee8d4270dd17ff748c30ed7304846fdc Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Fri, 20 May 2022 18:57:27 +0200 Subject: [PATCH] Add filename conformity check * Add util function to check a filename for confirming to csaf-v2.0-csd02. * Add code to reject bad filenames in provider, checker, aggregator and uploader. --- cmd/csaf_aggregator/mirror.go | 8 ++++++- cmd/csaf_checker/processor.go | 26 +++++++++++++++++++++- cmd/csaf_provider/actions.go | 4 ++++ cmd/csaf_uploader/main.go | 4 ++++ util/file.go | 26 +++++++++++++++------- util/file_test.go | 42 +++++++++++++++++++++++++++++++++++ 6 files changed, 100 insertions(+), 10 deletions(-) diff --git a/cmd/csaf_aggregator/mirror.go b/cmd/csaf_aggregator/mirror.go index 1e75e41b..e19d9097 100644 --- a/cmd/csaf_aggregator/mirror.go +++ b/cmd/csaf_aggregator/mirror.go @@ -419,7 +419,13 @@ func (w *worker) mirrorFiles(tlpLabel *csaf.TLPLabel, files []string) error { log.Printf("error: %s\n", err) continue } - filename := util.CleanFileName(filepath.Base(u.Path)) + + // Ignore not confirming filenames. + filename := filepath.Base(u.Path) + if !util.ConfirmingFileName(filename) { + log.Printf("Not confirming filename %q. Ignoring.\n", filename) + continue + } var advisory interface{} diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index d0ca72db..d9f16b2f 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -22,6 +22,7 @@ import ( "log" "net/http" "net/url" + "path/filepath" "regexp" "sort" "strconv" @@ -204,6 +205,7 @@ func (p *processor) checkDomain(domain string) error { (*processor).checkSecurity, (*processor).checkCSAFs, (*processor).checkMissing, + (*processor).checkInvalid, (*processor).checkListing, (*processor).checkWellknownMetadataReporter, (*processor).checkDNSPathReporter, @@ -724,7 +726,29 @@ func (p *processor) checkMissing(string) error { return nil } -// checkListing wents over all found adivisories URLs and checks, +// checkInvalid wents over all found adivisories URLs and checks +// if file name confirms to standard. +func (p *processor) checkInvalid(string) error { + + p.badDirListings.use() + var invalids []string + + for f := range p.alreadyChecked { + if !util.ConfirmingFileName(filepath.Base(f)) { + invalids = append(invalids, f) + } + } + + if len(invalids) > 0 { + sort.Strings(invalids) + p.badDirListings.add("advisories with invalid file names: %s", + strings.Join(invalids, ", ")) + } + + return nil +} + +// checkListing wents over all found adivisories URLs and checks // if their parent directory is listable. func (p *processor) checkListing(string) error { diff --git a/cmd/csaf_provider/actions.go b/cmd/csaf_provider/actions.go index c836fd33..b6a6436a 100644 --- a/cmd/csaf_provider/actions.go +++ b/cmd/csaf_provider/actions.go @@ -39,6 +39,10 @@ func (c *controller) loadCSAF(r *http.Request) (string, []byte, error) { } defer file.Close() + if !util.ConfirmingFileName(handler.Filename) { + return "", nil, errors.New("given csaf filename is not confirming") + } + var buf bytes.Buffer if _, err := io.Copy(&buf, c.cfg.uploadLimiter(file)); err != nil { return "", nil, err diff --git a/cmd/csaf_uploader/main.go b/cmd/csaf_uploader/main.go index cc2f8abd..9ac84efa 100644 --- a/cmd/csaf_uploader/main.go +++ b/cmd/csaf_uploader/main.go @@ -277,6 +277,10 @@ func (p *processor) uploadRequest(filename string) (*http.Request, error) { // It prints the response messages. func (p *processor) process(filename string) error { + if bn := filepath.Base(filename); !util.ConfirmingFileName(bn) { + return fmt.Errorf("%q is not a confirming file name", bn) + } + req, err := p.uploadRequest(filename) if err != nil { return err diff --git a/util/file.go b/util/file.go index fb2942ab..68b91aad 100644 --- a/util/file.go +++ b/util/file.go @@ -19,15 +19,25 @@ import ( "time" ) -var ( - twoOrMoreDots = regexp.MustCompile(`\.{2,}`) - stripSlashes = strings.NewReplacer(`/`, ``, `\`, ``) -) - -// CleanFileName removes the "/" "\" charachters and replace the two or more -// occurences of "." with only one from the passed string. +var invalidRune = regexp.MustCompile(`[^+\-a-z0-9]+`) // invalid runes + `_` + +// CleanFileName replaces invalid runes with an underscore and +// afterwards collapses multiple underscores into one. +// If the filename does not end with '.json' it will be appended. +// The filename is converted to lower case. +// https://docs.oasis-open.org/csaf/csaf/v2.0/csd02/csaf-v2.0-csd02.html#51-filename +// specifies valid runes as 'a' to 'z', '0' to '9' and '+', '-', '_'. func CleanFileName(s string) string { - return twoOrMoreDots.ReplaceAllString(stripSlashes.Replace(s), `.`) + s = strings.ToLower(s) + if strings.HasSuffix(s, ".json") { + s = s[:len(s)-len(".json")] + } + return invalidRune.ReplaceAllString(s, "_") + ".json" +} + +// ConfirmingFileName checks if the given filename is confirming the standard. +func ConfirmingFileName(fname string) bool { + return fname == CleanFileName(fname) } // PathExists returns true if path exits. diff --git a/util/file_test.go b/util/file_test.go index f94d4ac9..71b60a9c 100644 --- a/util/file_test.go +++ b/util/file_test.go @@ -5,6 +5,48 @@ import ( "testing" ) +func TestCleanFileName(t *testing.T) { + for _, x := range [][2]string{ + {`HELLO`, `hello.json`}, + {`hello`, `hello.json`}, + {`cisco-sa-20190513-secureboot.json`, `cisco-sa-20190513-secureboot.json`}, + {``, `.json`}, + {`..`, `_.json`}, + {`../..`, `_.json`}, + {`abc.html`, `abc_html.json`}, + {`abc_.htm__l`, `abc_htm_l.json`}, + {`foo+BAR`, `foo+bar.json`}, + } { + if got := CleanFileName(x[0]); got != x[1] { + t.Errorf("%q: Expected %q but got %q.", x[0], x[1], got) + } + } +} + +func TestConfirmingFileName(t *testing.T) { + for _, x := range []struct { + s string + b bool + }{ + {`HELLO`, false}, + {`hello`, false}, + {`cisco-sa-20190513-secureboot.json`, true}, + {`example_company_-_2019-yh3234.json`, true}, + {`rhba-2019_0024.json`, true}, + {`2022__01-a.json`, false}, + {``, false}, + {`..`, false}, + {`../..`, false}, + {`abc.html`, false}, + {`abc_.htm__l`, false}, + {`foo+BAR`, false}, + } { + if got := ConfirmingFileName(x.s); got != x.b { + t.Errorf("%q: Expected %t but got %t.", x.s, x.b, got) + } + } +} + func TestNWriter(t *testing.T) { msg := []byte("Gruß!\n")