From 804ad2c8f53cd0a27d99beca55c180e8c4dddf4d Mon Sep 17 00:00:00 2001 From: elsapet Date: Fri, 16 Feb 2024 18:08:37 +0200 Subject: [PATCH] feat(go): add log output neutralization --- rules/go/lang/log_output_neutralization.yml | 151 ++++++++++++++++++ .../go/lang/log_output_neutralization/test.js | 18 +++ .../testdata/main.go | 73 +++++++++ 3 files changed, 242 insertions(+) create mode 100644 rules/go/lang/log_output_neutralization.yml create mode 100644 tests/go/lang/log_output_neutralization/test.js create mode 100644 tests/go/lang/log_output_neutralization/testdata/main.go diff --git a/rules/go/lang/log_output_neutralization.yml b/rules/go/lang/log_output_neutralization.yml new file mode 100644 index 000000000..df03a250f --- /dev/null +++ b/rules/go/lang/log_output_neutralization.yml @@ -0,0 +1,151 @@ +imports: + - go_shared_lang_dynamic_request_input +patterns: + - pattern: | + $.$($); + filters: + - either: + - variable: CALLER + detection: go_lang_log_output_neutralization_logger + - variable: CALLER + detection: go_lang_log_output_neutralization_zerolog + - variable: METHOD + regex: \A(Fatal|Panic|Print)(f|ln)?\z + - variable: INPUT + detection: go_lang_log_output_neutralization_input + - pattern: | + $.$.$($); + filters: + - variable: ZEROLOG + detection: go_lang_log_output_neutralization_zerolog + - variable: EVENT + regex: \A(Info|Debug|Error|Trace|Fatal|Panic|Warn)\z + - variable: METHOD + values: + - Msg + - Msgf + - variable: INPUT + detection: go_lang_log_output_neutralization_input + - pattern: | + $.$($); + filters: + - either: + - variable: CALLER + detection: go_lang_log_output_neutralization_logrus + - variable: CALLER + detection: go_lang_log_output_neutralization_seelog + - variable: METHOD + regex: \A(WithFields\.)?(Info|Debug|Error|Trace|Fatal|Panic|Warn)\z + - variable: INPUT + detection: go_lang_log_output_neutralization_input + - pattern: | + $.$($); + filters: + - variable: CALLER + detection: go_lang_log_output_neutralization_glog + - variable: METHOD + regex: \A(Info|Warning|Error|Fatal)(Contex)?(Depth)?(f)?\z + - variable: INPUT + detection: go_lang_log_output_neutralization_input + - pattern: | + $.$($); + filters: + - variable: ZAP + detection: go_lang_log_output_neutralization_zap + - variable: METHOD + regex: \A(WithFields\.)?(Info|Log|Error|Fatal|DPanic|Warn)\z + - variable: INPUT + detection: go_lang_log_output_neutralization_input +auxiliary: + - id: go_lang_log_output_neutralization_logger + patterns: + - log.New(); + - log.Default(); + - id: go_lang_log_output_neutralization_zerolog + patterns: + - import $"github.com/rs/zerolog" + - | + import ( + $"github.com/rs/zerolog" + ) + - id: go_lang_log_output_neutralization_logrus + patterns: + - logrus.New(); + - import $"github.com/sirupsen/logrus" + - | + import ( + $"github.com/sirupsen/logrus" + ) + - id: go_lang_log_output_neutralization_zap + patterns: + - zap.$<_>().Sugar() + - zap.$<_>() + - id: go_lang_log_output_neutralization_seelog + patterns: + - import $"github.com/cihub/seelog" + - | + import ( + $"github.com/cihub/seelog" + ) + - id: go_lang_log_output_neutralization_glog + patterns: + - import $"github.com/golang/glog" + - | + import ( + $"github.com/golang/glog" + ) + - id: go_lang_log_output_neutralization_input + sanitizer: go_lang_log_output_neutralization_sanitizer + patterns: + - pattern: $ + filters: + - variable: INPUT + detection: go_shared_lang_dynamic_request_input + scope: cursor + - pattern: $$<...>$$<...>; + filters: + - not: + variable: FORMAT_STRING + string_regex: /A*?(\%q)*?/z + - variable: INPUT + detection: go_shared_lang_dynamic_request_input + scope: cursor + - id: go_lang_log_output_neutralization_sanitizer + patterns: + - strconv.Quote(); + - html.EscapeString(); + - url.QueryEscape(); + - strings.ReplaceAll(); +languages: + - go +metadata: + description: Missing output neutralization for logs + remediation_message: | + ## Description + + Logging untrusted and unsanitized input could lead to log injection vulnerabilities. + + ## Remediations + + ❌ Do not log unsanitized external input directly + + ✅ Use printf methods with %q format for logging external input + + ```go + dangerousInput := os.Args[0] + + logger.Printf("Args: %q", dangerousInput) + ``` + + ✅ Use manual method to escape external strings before logging + + ```go + dangerousInput := os.Args[0] + sanitizedInput := strconv.Quote(dangerousInput) + + logger.Print(sanitizedInput) + ``` + cwe_id: + - 117 + id: go_lang_log_output_neutralization + documentation_url: https://docs.bearer.com/reference/rules/go_lang_log_output_neutralization diff --git a/tests/go/lang/log_output_neutralization/test.js b/tests/go/lang/log_output_neutralization/test.js new file mode 100644 index 000000000..81e7e0ad5 --- /dev/null +++ b/tests/go/lang/log_output_neutralization/test.js @@ -0,0 +1,18 @@ +const { + createNewInvoker, + getEnvironment, +} = require("../../../helper.js") +const { ruleId, ruleFile, testBase } = getEnvironment(__dirname) + +describe(ruleId, () => { + const invoke = createNewInvoker(ruleId, ruleFile, testBase) + + test("log_output_neutralization", () => { + const testCase = "main.go" + + const results = invoke(testCase) + + expect(results.Missing).toEqual([]) + expect(results.Extra).toEqual([]) + }) +}) \ No newline at end of file diff --git a/tests/go/lang/log_output_neutralization/testdata/main.go b/tests/go/lang/log_output_neutralization/testdata/main.go new file mode 100644 index 000000000..58432e587 --- /dev/null +++ b/tests/go/lang/log_output_neutralization/testdata/main.go @@ -0,0 +1,73 @@ +// Use bearer:expected go_lang_log_output_neutralization to flag expected findings + +package main + +import ( + "bytes" + "os" + + "log" + + seelog "github.com/cihub/seelog" + glog "github.com/golang/glog" + zerolog "github.com/rs/zerolog" + logrus "github.com/sirupsen/logrus" + "go.uber.org/zap" + + "strconv" +) + +func bad() { + var buf bytes.Buffer + logger := log.New(&buf, "logger: ", log.Lshortfile) + + // bearer:expected go_lang_log_output_neutralization + logger.Print(os.Args[0]) +} + +func bad2() { + // bearer:expected go_lang_log_output_neutralization + zerolog.Info.Msg(os.Args[0]) + // bearer:expected go_lang_log_output_neutralization + zerolog.Print(os.Args[0]) +} + +func bad3() { + // bearer:expected go_lang_log_output_neutralization + logrus.WithFields().Info(os.Args[0]) + + log := logrus.New() + // bearer:expected go_lang_log_output_neutralization + log.Error(os.Args[0]) +} + +func bad4() { + var cfg zap.Config + logger := zap.Must(cfg.Build()) + defer logger.Sync() + + // bearer:expected go_lang_log_output_neutralization + logger.Info(os.Args[0]) + logger.Printf("Args: %s", os.Args[0]) +} + +func bad5() { + // bearer:expected go_lang_log_output_neutralization + seelog.Trace(os.Args[0]) +} + +func bad6() { + // bearer:expected go_lang_log_output_neutralization + glog.Fatalf(os.Args[0]) +} + +func ok(input string) { + var buf bytes.Buffer + logger := log.New(&buf, "logger: ", log.Lshortfile) + sanitizedInput := strconv.Quote(input) + + logger.Print(sanitizedInput) + logger.Printf("Args: %q", os.Args[0]) + + zerolog.Info.Msg(sanitizedInput) +}