mirror of
https://github.com/securego/gosec.git
synced 2026-06-20 00:15:59 +02:00
Add G707 taint analyzer for SMTP command/header injection (#1535)
This change introduces a new taint-analysis rule, G707, to detect potential SMTP command/header injection when untrusted input reaches net/smtp sink. Signed-off-by: Cosmin Cojocar <cosmin@cojocar.ch>
This commit is contained in:
@@ -118,5 +118,9 @@ var _ = Describe("gosec analyzers", func() {
|
||||
It("should detect log injection via taint analysis", func() {
|
||||
runner("G706", testutils.SampleCodeG706)
|
||||
})
|
||||
|
||||
It("should detect SMTP command/header injection via taint analysis", func() {
|
||||
runner("G707", testutils.SampleCodeG707)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -74,6 +74,13 @@ var (
|
||||
Severity: "LOW",
|
||||
CWE: "CWE-117",
|
||||
}
|
||||
|
||||
SMTPInjectionRule = taint.RuleInfo{
|
||||
ID: "G707",
|
||||
Description: "SMTP command/header injection via user input",
|
||||
Severity: "HIGH",
|
||||
CWE: "CWE-93",
|
||||
}
|
||||
)
|
||||
|
||||
// AnalyzerList contains a mapping of analyzer ID's to analyzer definitions and a mapping
|
||||
@@ -130,6 +137,7 @@ var defaultAnalyzers = []AnalyzerDefinition{
|
||||
{"G704", "SSRF via taint analysis", newSSRFAnalyzer},
|
||||
{"G705", "XSS via taint analysis", newXSSAnalyzer},
|
||||
{"G706", "Log injection via taint analysis", newLogInjectionAnalyzer},
|
||||
{"G707", "SMTP command/header injection via taint analysis", newSMTPInjectionAnalyzer},
|
||||
}
|
||||
|
||||
// Generate the list of analyzers to use
|
||||
@@ -163,6 +171,7 @@ func DefaultTaintAnalyzers() []*analysis.Analyzer {
|
||||
ssrfConfig := SSRF()
|
||||
xssConfig := XSS()
|
||||
logConfig := LogInjection()
|
||||
smtpConfig := SMTPInjection()
|
||||
|
||||
return []*analysis.Analyzer{
|
||||
taint.NewGosecAnalyzer(&SQLInjectionRule, &sqlConfig),
|
||||
@@ -171,5 +180,6 @@ func DefaultTaintAnalyzers() []*analysis.Analyzer {
|
||||
taint.NewGosecAnalyzer(&SSRFRule, &ssrfConfig),
|
||||
taint.NewGosecAnalyzer(&XSSRule, &xssConfig),
|
||||
taint.NewGosecAnalyzer(&LogInjectionRule, &logConfig),
|
||||
taint.NewGosecAnalyzer(&SMTPInjectionRule, &smtpConfig),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,12 @@ func TestTaintAnalyzerConstructors(t *testing.T) {
|
||||
id: "G706",
|
||||
description: "Log injection via taint analysis",
|
||||
},
|
||||
{
|
||||
name: "SMTPInjection",
|
||||
constructor: newSMTPInjectionAnalyzer,
|
||||
id: "G707",
|
||||
description: "SMTP command/header injection via taint analysis",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -89,7 +95,7 @@ func TestTaintAnalyzerConstructors(t *testing.T) {
|
||||
|
||||
// TestDefaultAnalyzersIncludeTaint tests that default analyzers include taint rules.
|
||||
func TestDefaultAnalyzersIncludeTaint(t *testing.T) {
|
||||
expectedTaintIDs := []string{"G701", "G702", "G703", "G704", "G705", "G706"}
|
||||
expectedTaintIDs := []string{"G701", "G702", "G703", "G704", "G705", "G706", "G707"}
|
||||
|
||||
found := make(map[string]bool)
|
||||
for _, def := range defaultAnalyzers {
|
||||
@@ -107,7 +113,7 @@ func TestDefaultAnalyzersIncludeTaint(t *testing.T) {
|
||||
func TestGenerateIncludesTaintAnalyzers(t *testing.T) {
|
||||
analyzerList := Generate(false)
|
||||
|
||||
expectedTaintIDs := []string{"G701", "G702", "G703", "G704", "G705", "G706"}
|
||||
expectedTaintIDs := []string{"G701", "G702", "G703", "G704", "G705", "G706", "G707"}
|
||||
|
||||
for _, id := range expectedTaintIDs {
|
||||
if _, ok := analyzerList.Analyzers[id]; !ok {
|
||||
@@ -272,7 +278,7 @@ func TestAnalyzerList_AnalyzersInfo(t *testing.T) {
|
||||
func TestDefaultTaintAnalyzers(t *testing.T) {
|
||||
analyzers := DefaultTaintAnalyzers()
|
||||
|
||||
expectedCount := 6 // SQL, Command, Path, SSRF, XSS, Log
|
||||
expectedCount := 7 // SQL, Command, Path, SSRF, XSS, Log, SMTP
|
||||
if len(analyzers) != expectedCount {
|
||||
t.Errorf("Expected %d taint analyzers, got %d", expectedCount, len(analyzers))
|
||||
}
|
||||
@@ -284,6 +290,7 @@ func TestDefaultTaintAnalyzers(t *testing.T) {
|
||||
"G704": false,
|
||||
"G705": false,
|
||||
"G706": false,
|
||||
"G707": false,
|
||||
}
|
||||
|
||||
for _, analyzer := range analyzers {
|
||||
@@ -390,4 +397,13 @@ func TestTaintRuleConstants(t *testing.T) {
|
||||
t.Errorf("CWE = %s, want CWE-117", LogInjectionRule.CWE)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SMTPInjection", func(t *testing.T) {
|
||||
if SMTPInjectionRule.ID != "G707" {
|
||||
t.Errorf("ID = %s, want G707", SMTPInjectionRule.ID)
|
||||
}
|
||||
if SMTPInjectionRule.CWE != "CWE-93" {
|
||||
t.Errorf("CWE = %s, want CWE-93", SMTPInjectionRule.CWE)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// (c) Copyright gosec's authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package analyzers
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
||||
"github.com/securego/gosec/v2/taint"
|
||||
)
|
||||
|
||||
// SMTPInjection returns a configuration for detecting SMTP command/header injection vulnerabilities.
|
||||
func SMTPInjection() taint.Config {
|
||||
return taint.Config{
|
||||
Sources: []taint.Source{
|
||||
// Type sources: tainted when received as parameters
|
||||
{Package: "net/http", Name: "Request", Pointer: true},
|
||||
{Package: "net/url", Name: "URL", Pointer: true},
|
||||
{Package: "net/url", Name: "Values"},
|
||||
{Package: "bufio", Name: "Reader", Pointer: true},
|
||||
{Package: "bufio", Name: "Scanner", Pointer: true},
|
||||
|
||||
// Function sources
|
||||
{Package: "os", Name: "Args", IsFunc: true},
|
||||
{Package: "os", Name: "Getenv", IsFunc: true},
|
||||
},
|
||||
Sinks: []taint.Sink{
|
||||
// net/smtp.SendMail(addr, auth, from, to, msg)
|
||||
// Check sender and recipient envelope fields.
|
||||
{Package: "net/smtp", Method: "SendMail", CheckArgs: []int{2, 3}},
|
||||
|
||||
// For smtp.Client methods, Args[0] is receiver.
|
||||
{Package: "net/smtp", Receiver: "Client", Method: "Mail", Pointer: true, CheckArgs: []int{1}},
|
||||
{Package: "net/smtp", Receiver: "Client", Method: "Rcpt", Pointer: true, CheckArgs: []int{1}},
|
||||
},
|
||||
Sanitizers: []taint.Sanitizer{
|
||||
// net/mail parsers enforce RFC-compatible mailbox/address syntax.
|
||||
{Package: "net/mail", Method: "ParseAddress"},
|
||||
{Package: "net/mail", Method: "ParseAddressList"},
|
||||
|
||||
// AddressParser methods also provide structured parsing.
|
||||
{Package: "net/mail", Receiver: "AddressParser", Method: "Parse", Pointer: true},
|
||||
{Package: "net/mail", Receiver: "AddressParser", Method: "ParseList", Pointer: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// newSMTPInjectionAnalyzer creates an analyzer for detecting SMTP injection vulnerabilities
|
||||
// via taint analysis (G707)
|
||||
func newSMTPInjectionAnalyzer(id string, description string) *analysis.Analyzer {
|
||||
config := SMTPInjection()
|
||||
rule := SMTPInjectionRule
|
||||
rule.ID = id
|
||||
rule.Description = description
|
||||
return taint.NewGosecAnalyzer(&rule, &config)
|
||||
}
|
||||
Reference in New Issue
Block a user