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:
Cosmin Cojocar
2026-02-21 11:12:34 +01:00
committed by GitHub
parent f61ed314c2
commit c13a48626b
8 changed files with 183 additions and 3 deletions
+4
View File
@@ -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)
})
})
})
+10
View File
@@ -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),
}
}
+19 -3
View File
@@ -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)
}
})
}
+67
View File
@@ -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)
}