Add Email Injection tests for reverse flow models

This commit is contained in:
Owen Mansel-Chan
2025-09-18 11:56:01 +01:00
parent 9892836f14
commit a0c647ce83
4 changed files with 137 additions and 72 deletions

View File

@@ -6,8 +6,8 @@ import (
)
func mail(w http.ResponseWriter, r *http.Request) {
host := r.Header.Get("Host")
host := r.Header.Get("Host") // $ Source
token := backend.getUserSecretResetToken(email)
body := "Click to reset password: " + host + "/" + token
smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body))
smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body)) // $ Alert
}

View File

@@ -1,66 +1,94 @@
#select
| EmailBad.go:12:56:12:67 | type conversion | EmailBad.go:9:10:9:17 | selection of Header | EmailBad.go:12:56:12:67 | type conversion | Email content may contain $@. | EmailBad.go:9:10:9:17 | selection of Header | untrusted input |
| main.go:31:57:31:78 | type conversion | main.go:29:21:29:31 | call to Referer | main.go:31:57:31:78 | type conversion | Email content may contain $@. | main.go:29:21:29:31 | call to Referer | untrusted input |
| main.go:40:3:40:7 | definition of write | main.go:37:21:37:31 | call to Referer | main.go:40:3:40:7 | definition of write | Email content may contain $@. | main.go:37:21:37:31 | call to Referer | untrusted input |
| main.go:52:46:52:59 | untrustedInput | main.go:46:21:46:31 | call to Referer | main.go:52:46:52:59 | untrustedInput | Email content may contain $@. | main.go:46:21:46:31 | call to Referer | untrusted input |
| main.go:53:52:53:65 | untrustedInput | main.go:46:21:46:31 | call to Referer | main.go:53:52:53:65 | untrustedInput | Email content may contain $@. | main.go:46:21:46:31 | call to Referer | untrusted input |
| main.go:63:16:63:22 | content | main.go:58:21:58:31 | call to Referer | main.go:63:16:63:22 | content | Email content may contain $@. | main.go:58:21:58:31 | call to Referer | untrusted input |
| main.go:76:50:76:56 | content | main.go:68:21:68:31 | call to Referer | main.go:76:50:76:56 | content | Email content may contain $@. | main.go:68:21:68:31 | call to Referer | untrusted input |
| main.go:76:59:76:65 | content | main.go:68:21:68:31 | call to Referer | main.go:76:59:76:65 | content | Email content may contain $@. | main.go:68:21:68:31 | call to Referer | untrusted input |
| main.go:77:16:77:22 | content | main.go:68:21:68:31 | call to Referer | main.go:77:16:77:22 | content | Email content may contain $@. | main.go:68:21:68:31 | call to Referer | untrusted input |
| main.go:89:37:89:50 | untrustedInput | main.go:82:21:82:31 | call to Referer | main.go:89:37:89:50 | untrustedInput | Email content may contain $@. | main.go:82:21:82:31 | call to Referer | untrusted input |
| main.go:93:16:93:23 | content2 | main.go:82:21:82:31 | call to Referer | main.go:93:16:93:23 | content2 | Email content may contain $@. | main.go:82:21:82:31 | call to Referer | untrusted input |
| main.go:33:57:33:78 | type conversion | main.go:31:21:31:31 | call to Referer | main.go:33:57:33:78 | type conversion | Email content may contain $@. | main.go:31:21:31:31 | call to Referer | untrusted input |
| main.go:42:3:42:7 | definition of write | main.go:39:21:39:31 | call to Referer | main.go:42:3:42:7 | definition of write | Email content may contain $@. | main.go:39:21:39:31 | call to Referer | untrusted input |
| main.go:54:46:54:59 | untrustedInput | main.go:48:21:48:31 | call to Referer | main.go:54:46:54:59 | untrustedInput | Email content may contain $@. | main.go:48:21:48:31 | call to Referer | untrusted input |
| main.go:55:52:55:65 | untrustedInput | main.go:48:21:48:31 | call to Referer | main.go:55:52:55:65 | untrustedInput | Email content may contain $@. | main.go:48:21:48:31 | call to Referer | untrusted input |
| main.go:65:16:65:22 | content | main.go:60:21:60:31 | call to Referer | main.go:65:16:65:22 | content | Email content may contain $@. | main.go:60:21:60:31 | call to Referer | untrusted input |
| main.go:78:50:78:56 | content | main.go:70:21:70:31 | call to Referer | main.go:78:50:78:56 | content | Email content may contain $@. | main.go:70:21:70:31 | call to Referer | untrusted input |
| main.go:78:59:78:65 | content | main.go:70:21:70:31 | call to Referer | main.go:78:59:78:65 | content | Email content may contain $@. | main.go:70:21:70:31 | call to Referer | untrusted input |
| main.go:79:16:79:22 | content | main.go:70:21:70:31 | call to Referer | main.go:79:16:79:22 | content | Email content may contain $@. | main.go:70:21:70:31 | call to Referer | untrusted input |
| main.go:91:37:91:50 | untrustedInput | main.go:84:21:84:31 | call to Referer | main.go:91:37:91:50 | untrustedInput | Email content may contain $@. | main.go:84:21:84:31 | call to Referer | untrusted input |
| main.go:95:16:95:23 | content2 | main.go:84:21:84:31 | call to Referer | main.go:95:16:95:23 | content2 | Email content may contain $@. | main.go:84:21:84:31 | call to Referer | untrusted input |
| main.go:124:57:124:65 | call to Bytes | main.go:113:21:113:31 | call to Referer | main.go:124:57:124:65 | call to Bytes | Email content may contain $@. | main.go:113:21:113:31 | call to Referer | untrusted input |
| main.go:141:57:141:65 | call to Bytes | main.go:129:21:129:31 | call to Referer | main.go:141:57:141:65 | call to Bytes | Email content may contain $@. | main.go:129:21:129:31 | call to Referer | untrusted input |
edges
| EmailBad.go:9:10:9:17 | selection of Header | EmailBad.go:9:10:9:29 | call to Get | provenance | Src:MaD:1 MaD:5 |
| EmailBad.go:9:10:9:17 | selection of Header | EmailBad.go:9:10:9:29 | call to Get | provenance | Src:MaD:1 MaD:7 |
| EmailBad.go:9:10:9:29 | call to Get | EmailBad.go:12:56:12:67 | type conversion | provenance | |
| main.go:29:21:29:31 | call to Referer | main.go:31:57:31:78 | type conversion | provenance | Src:MaD:2 |
| main.go:37:21:37:31 | call to Referer | main.go:41:25:41:38 | untrustedInput | provenance | Src:MaD:2 |
| main.go:41:25:41:38 | untrustedInput | main.go:40:3:40:7 | definition of write | provenance | MaD:4 |
| main.go:46:21:46:31 | call to Referer | main.go:52:46:52:59 | untrustedInput | provenance | Src:MaD:2 |
| main.go:46:21:46:31 | call to Referer | main.go:53:52:53:65 | untrustedInput | provenance | Src:MaD:2 |
| main.go:58:21:58:31 | call to Referer | main.go:60:47:60:60 | untrustedInput | provenance | Src:MaD:2 |
| main.go:60:14:60:61 | call to NewContent | main.go:63:16:63:22 | content | provenance | |
| main.go:60:47:60:60 | untrustedInput | main.go:60:14:60:61 | call to NewContent | provenance | MaD:3 |
| main.go:68:21:68:31 | call to Referer | main.go:74:47:74:60 | untrustedInput | provenance | Src:MaD:2 |
| main.go:74:14:74:61 | call to NewContent | main.go:76:50:76:56 | content | provenance | |
| main.go:74:14:74:61 | call to NewContent | main.go:76:59:76:65 | content | provenance | |
| main.go:74:14:74:61 | call to NewContent | main.go:77:16:77:22 | content | provenance | |
| main.go:74:47:74:60 | untrustedInput | main.go:74:14:74:61 | call to NewContent | provenance | MaD:3 |
| main.go:82:21:82:31 | call to Referer | main.go:89:37:89:50 | untrustedInput | provenance | Src:MaD:2 |
| main.go:82:21:82:31 | call to Referer | main.go:91:48:91:61 | untrustedInput | provenance | Src:MaD:2 |
| main.go:91:15:91:62 | call to NewContent | main.go:93:16:93:23 | content2 | provenance | |
| main.go:91:48:91:61 | untrustedInput | main.go:91:15:91:62 | call to NewContent | provenance | MaD:3 |
| main.go:31:21:31:31 | call to Referer | main.go:33:57:33:78 | type conversion | provenance | Src:MaD:2 |
| main.go:39:21:39:31 | call to Referer | main.go:43:25:43:38 | untrustedInput | provenance | Src:MaD:2 |
| main.go:43:25:43:38 | untrustedInput | main.go:42:3:42:7 | definition of write | provenance | MaD:5 |
| main.go:48:21:48:31 | call to Referer | main.go:54:46:54:59 | untrustedInput | provenance | Src:MaD:2 |
| main.go:48:21:48:31 | call to Referer | main.go:55:52:55:65 | untrustedInput | provenance | Src:MaD:2 |
| main.go:60:21:60:31 | call to Referer | main.go:62:47:62:60 | untrustedInput | provenance | Src:MaD:2 |
| main.go:62:14:62:61 | call to NewContent | main.go:65:16:65:22 | content | provenance | |
| main.go:62:47:62:60 | untrustedInput | main.go:62:14:62:61 | call to NewContent | provenance | MaD:4 |
| main.go:70:21:70:31 | call to Referer | main.go:76:47:76:60 | untrustedInput | provenance | Src:MaD:2 |
| main.go:76:14:76:61 | call to NewContent | main.go:78:50:78:56 | content | provenance | |
| main.go:76:14:76:61 | call to NewContent | main.go:78:59:78:65 | content | provenance | |
| main.go:76:14:76:61 | call to NewContent | main.go:79:16:79:22 | content | provenance | |
| main.go:76:47:76:60 | untrustedInput | main.go:76:14:76:61 | call to NewContent | provenance | MaD:4 |
| main.go:84:21:84:31 | call to Referer | main.go:91:37:91:50 | untrustedInput | provenance | Src:MaD:2 |
| main.go:84:21:84:31 | call to Referer | main.go:93:48:93:61 | untrustedInput | provenance | Src:MaD:2 |
| main.go:93:15:93:62 | call to NewContent | main.go:95:16:95:23 | content2 | provenance | |
| main.go:93:48:93:61 | untrustedInput | main.go:93:15:93:62 | call to NewContent | provenance | MaD:4 |
| main.go:113:21:113:31 | call to Referer | main.go:119:28:119:41 | untrustedInput | provenance | Src:MaD:2 |
| main.go:116:3:116:4 | definition of mw | main.go:116:29:116:30 | &... | provenance | FunctionModel |
| main.go:116:29:116:30 | &... | main.go:124:57:124:57 | b | provenance | |
| main.go:119:28:119:41 | untrustedInput | main.go:116:3:116:4 | definition of mw | provenance | MaD:6 |
| main.go:124:57:124:57 | b | main.go:124:57:124:65 | call to Bytes | provenance | MaD:3 |
| main.go:129:21:129:31 | call to Referer | main.go:136:30:136:43 | untrustedInput | provenance | Src:MaD:2 |
| main.go:132:3:132:4 | definition of mw | main.go:132:29:132:30 | &... | provenance | FunctionModel |
| main.go:132:29:132:30 | &... | main.go:141:57:141:57 | b | provenance | |
| main.go:135:3:135:12 | definition of formWriter | main.go:132:3:132:4 | definition of mw | provenance | FunctionModel |
| main.go:136:30:136:43 | untrustedInput | main.go:135:3:135:12 | definition of formWriter | provenance | MaD:5 |
| main.go:141:57:141:57 | b | main.go:141:57:141:65 | call to Bytes | provenance | MaD:3 |
models
| 1 | Source: net/http; Request; true; Header; ; ; ; remote; manual |
| 2 | Source: net/http; Request; true; Referer; ; ; ReturnValue; remote; manual |
| 3 | Summary: github.com/sendgrid/sendgrid-go/helpers/mail; ; false; NewContent; ; ; Argument[1]; ReturnValue; taint; manual |
| 4 | Summary: io; ; false; WriteString; ; ; Argument[1]; Argument[0]; taint; manual |
| 5 | Summary: net/http; Header; true; Get; ; ; Argument[receiver]; ReturnValue; taint; manual |
| 3 | Summary: bytes; Buffer; true; Bytes; ; ; Argument[receiver]; ReturnValue; taint; manual |
| 4 | Summary: github.com/sendgrid/sendgrid-go/helpers/mail; ; false; NewContent; ; ; Argument[1]; ReturnValue; taint; manual |
| 5 | Summary: io; ; false; WriteString; ; ; Argument[1]; Argument[0]; taint; manual |
| 6 | Summary: mime/multipart; Writer; true; WriteField; ; ; Argument[0..1]; Argument[receiver]; taint; manual |
| 7 | Summary: net/http; Header; true; Get; ; ; Argument[receiver]; ReturnValue; taint; manual |
nodes
| EmailBad.go:9:10:9:17 | selection of Header | semmle.label | selection of Header |
| EmailBad.go:9:10:9:29 | call to Get | semmle.label | call to Get |
| EmailBad.go:12:56:12:67 | type conversion | semmle.label | type conversion |
| main.go:29:21:29:31 | call to Referer | semmle.label | call to Referer |
| main.go:31:57:31:78 | type conversion | semmle.label | type conversion |
| main.go:37:21:37:31 | call to Referer | semmle.label | call to Referer |
| main.go:40:3:40:7 | definition of write | semmle.label | definition of write |
| main.go:41:25:41:38 | untrustedInput | semmle.label | untrustedInput |
| main.go:46:21:46:31 | call to Referer | semmle.label | call to Referer |
| main.go:52:46:52:59 | untrustedInput | semmle.label | untrustedInput |
| main.go:53:52:53:65 | untrustedInput | semmle.label | untrustedInput |
| main.go:58:21:58:31 | call to Referer | semmle.label | call to Referer |
| main.go:60:14:60:61 | call to NewContent | semmle.label | call to NewContent |
| main.go:60:47:60:60 | untrustedInput | semmle.label | untrustedInput |
| main.go:63:16:63:22 | content | semmle.label | content |
| main.go:68:21:68:31 | call to Referer | semmle.label | call to Referer |
| main.go:74:14:74:61 | call to NewContent | semmle.label | call to NewContent |
| main.go:74:47:74:60 | untrustedInput | semmle.label | untrustedInput |
| main.go:76:50:76:56 | content | semmle.label | content |
| main.go:76:59:76:65 | content | semmle.label | content |
| main.go:77:16:77:22 | content | semmle.label | content |
| main.go:82:21:82:31 | call to Referer | semmle.label | call to Referer |
| main.go:89:37:89:50 | untrustedInput | semmle.label | untrustedInput |
| main.go:91:15:91:62 | call to NewContent | semmle.label | call to NewContent |
| main.go:91:48:91:61 | untrustedInput | semmle.label | untrustedInput |
| main.go:93:16:93:23 | content2 | semmle.label | content2 |
| main.go:31:21:31:31 | call to Referer | semmle.label | call to Referer |
| main.go:33:57:33:78 | type conversion | semmle.label | type conversion |
| main.go:39:21:39:31 | call to Referer | semmle.label | call to Referer |
| main.go:42:3:42:7 | definition of write | semmle.label | definition of write |
| main.go:43:25:43:38 | untrustedInput | semmle.label | untrustedInput |
| main.go:48:21:48:31 | call to Referer | semmle.label | call to Referer |
| main.go:54:46:54:59 | untrustedInput | semmle.label | untrustedInput |
| main.go:55:52:55:65 | untrustedInput | semmle.label | untrustedInput |
| main.go:60:21:60:31 | call to Referer | semmle.label | call to Referer |
| main.go:62:14:62:61 | call to NewContent | semmle.label | call to NewContent |
| main.go:62:47:62:60 | untrustedInput | semmle.label | untrustedInput |
| main.go:65:16:65:22 | content | semmle.label | content |
| main.go:70:21:70:31 | call to Referer | semmle.label | call to Referer |
| main.go:76:14:76:61 | call to NewContent | semmle.label | call to NewContent |
| main.go:76:47:76:60 | untrustedInput | semmle.label | untrustedInput |
| main.go:78:50:78:56 | content | semmle.label | content |
| main.go:78:59:78:65 | content | semmle.label | content |
| main.go:79:16:79:22 | content | semmle.label | content |
| main.go:84:21:84:31 | call to Referer | semmle.label | call to Referer |
| main.go:91:37:91:50 | untrustedInput | semmle.label | untrustedInput |
| main.go:93:15:93:62 | call to NewContent | semmle.label | call to NewContent |
| main.go:93:48:93:61 | untrustedInput | semmle.label | untrustedInput |
| main.go:95:16:95:23 | content2 | semmle.label | content2 |
| main.go:113:21:113:31 | call to Referer | semmle.label | call to Referer |
| main.go:116:3:116:4 | definition of mw | semmle.label | definition of mw |
| main.go:116:29:116:30 | &... | semmle.label | &... |
| main.go:119:28:119:41 | untrustedInput | semmle.label | untrustedInput |
| main.go:124:57:124:57 | b | semmle.label | b |
| main.go:124:57:124:65 | call to Bytes | semmle.label | call to Bytes |
| main.go:129:21:129:31 | call to Referer | semmle.label | call to Referer |
| main.go:132:3:132:4 | definition of mw | semmle.label | definition of mw |
| main.go:132:29:132:30 | &... | semmle.label | &... |
| main.go:135:3:135:12 | definition of formWriter | semmle.label | definition of formWriter |
| main.go:136:30:136:43 | untrustedInput | semmle.label | untrustedInput |
| main.go:141:57:141:57 | b | semmle.label | b |
| main.go:141:57:141:65 | call to Bytes | semmle.label | call to Bytes |
subpaths

View File

@@ -1,2 +1,4 @@
query: Security/CWE-640/EmailInjection.ql
postprocess: utils/test/PrettyPrintModels.ql
postprocess:
- utils/test/PrettyPrintModels.ql
- utils/test/InlineExpectationsTestQuery.ql

View File

@@ -3,11 +3,13 @@ package main
//go:generate depstubber -vendor github.com/sendgrid/sendgrid-go/helpers/mail "" NewEmail,NewSingleEmail,NewContent,NewV3Mail,NewV3MailInit
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"io"
"log"
"mime/multipart"
"net/http"
"net/smtp"
@@ -26,46 +28,46 @@ func main() {
// Not OK
http.HandleFunc("/ex0", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
untrustedInput := r.Referer() // $ Source
smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(untrustedInput))
smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(untrustedInput)) // $ Alert
})
// Not OK
http.HandleFunc("/ex1", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
untrustedInput := r.Referer() // $ Source
s, _ := smtp.Dial("test.test")
write, _ := s.Data()
write, _ := s.Data() // $ Alert
io.WriteString(write, untrustedInput)
})
// Not OK
http.HandleFunc("/ex2", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
untrustedInput := r.Referer() // $ Source
from := sendgrid.NewEmail("from", "from@from.com")
to := sendgrid.NewEmail("to", "to@to.com")
subject := "test"
body := "body"
sendgrid.NewSingleEmail(from, subject, to, untrustedInput, body)
sendgrid.NewSingleEmail(from, subject, to, body, untrustedInput)
sendgrid.NewSingleEmail(from, subject, to, untrustedInput, body) // $ Alert
sendgrid.NewSingleEmail(from, subject, to, body, untrustedInput) // $ Alert
})
// Not OK
http.HandleFunc("/ex3", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
untrustedInput := r.Referer() // $ Source
content := sendgrid.NewContent("text/html", untrustedInput)
v := sendgrid.NewV3Mail()
v.AddContent(content)
v.AddContent(content) // $ Alert
})
// Not OK
http.HandleFunc("/ex4", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
untrustedInput := r.Referer() // $ Source
from := sendgrid.NewEmail("from", "from@from.com")
to := sendgrid.NewEmail("to", "to@to.com")
@@ -73,24 +75,24 @@ func main() {
content := sendgrid.NewContent("text/html", untrustedInput)
v := sendgrid.NewV3MailInit(from, subject, to, content, content)
v.AddContent(content)
v := sendgrid.NewV3MailInit(from, subject, to, content, content) // $ Alert
v.AddContent(content) // $ Alert
})
// Not OK
http.HandleFunc("/ex5", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer()
untrustedInput := r.Referer() // $ Source
from := sendgrid.NewEmail("from", "from@from.com")
to := sendgrid.NewEmail("to", "to@to.com")
content := sendgrid.NewContent("text/html", "test")
v := sendgrid.NewV3MailInit(from, untrustedInput, to, content, content)
v := sendgrid.NewV3MailInit(from, untrustedInput, to, content, content) // $ Alert
content2 := sendgrid.NewContent("text/html", untrustedInput)
v.AddContent(content2)
v.AddContent(content2) // $ Alert
})
// OK
@@ -106,6 +108,39 @@ func main() {
smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(signature))
})
// Not OK - mime/multipart.New.Writer test
http.HandleFunc("/multipart1", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer() // $ Source
var b bytes.Buffer
mw := multipart.NewWriter(&b)
// Add user-controlled data directly to the multipart writer
mw.WriteField("message", untrustedInput) // Injection point
mw.Close()
// Send the potentially malicious email content
smtp.SendMail("test.test", nil, "from@from.com", nil, b.Bytes()) // $ Alert
})
// Not OK - alternative multipart test
http.HandleFunc("/multipart2", func(w http.ResponseWriter, r *http.Request) {
untrustedInput := r.Referer() // $ Source
var b bytes.Buffer
mw := multipart.NewWriter(&b)
// Create form file with untrusted content
formWriter, _ := mw.CreateFormFile("attachment", "message.txt")
io.WriteString(formWriter, untrustedInput) // Injection point
mw.Close()
// Send email with user-controlled form file content
smtp.SendMail("test.test", nil, "from@from.com", nil, b.Bytes()) // $ Alert
})
log.Println(http.ListenAndServe(":80", nil))
}