diff --git a/change-notes/2020-06-16-email-injection.md b/change-notes/2020-06-16-email-injection.md new file mode 100644 index 00000000000..63abd57f2d6 --- /dev/null +++ b/change-notes/2020-06-16-email-injection.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The query "Email injection" (`go/email-injection`) has been moved out of the experimental folder. The query detects when untrusted input can be incorporated directly into an email. diff --git a/ql/src/experimental/CWE-640/EmailBad.go b/ql/src/Security/CWE-640/EmailBad.go similarity index 100% rename from ql/src/experimental/CWE-640/EmailBad.go rename to ql/src/Security/CWE-640/EmailBad.go diff --git a/ql/src/experimental/CWE-640/EmailGood.go b/ql/src/Security/CWE-640/EmailGood.go similarity index 100% rename from ql/src/experimental/CWE-640/EmailGood.go rename to ql/src/Security/CWE-640/EmailGood.go diff --git a/ql/src/experimental/CWE-640/EmailInjection.qhelp b/ql/src/Security/CWE-640/EmailInjection.qhelp similarity index 81% rename from ql/src/experimental/CWE-640/EmailInjection.qhelp rename to ql/src/Security/CWE-640/EmailInjection.qhelp index f3ac1dc4bbe..1da839cedb8 100644 --- a/ql/src/experimental/CWE-640/EmailInjection.qhelp +++ b/ql/src/Security/CWE-640/EmailInjection.qhelp @@ -4,7 +4,7 @@

Using untrusted input to construct an email can cause multiple security vulnerabilities. For instance, inclusion of an untrusted input in an email body - may allow an attacker to conduct Cross Site Scripting (XSS) attacks, while + may allow an attacker to conduct cross-site scripting (XSS) attacks, while inclusion of an HTTP header may allow a full account compromise as shown in the example below.

@@ -19,13 +19,13 @@ In the following example snippet, the host field is user controlled.

- A malicious user can send an HTTP request to the targeted web site, - but with a Host header that refers to their own web site. This means the + A malicious user can send an HTTP request to the targeted website, + but with a Host header that refers to their own website. This means the emails will be sent out to potential victims, originating from a server - they trust, but with links leading to a malicious web site. + they trust, but with links leading to a malicious website.

- If the email contains a password reset link, and should the victim click + If the email contains a password reset link, and the victim clicks the link, the secret reset token will be leaked to the attacker. Using the leaked token, the attacker can then construct the real reset link and use it to change the victim's password. @@ -38,7 +38,7 @@

  • - OWASP + OWASP: Content Spoofing .
  • diff --git a/ql/src/experimental/CWE-640/EmailInjection.ql b/ql/src/Security/CWE-640/EmailInjection.ql similarity index 97% rename from ql/src/experimental/CWE-640/EmailInjection.ql rename to ql/src/Security/CWE-640/EmailInjection.ql index 7cf11fa1a3e..1ae4d751342 100644 --- a/ql/src/experimental/CWE-640/EmailInjection.ql +++ b/ql/src/Security/CWE-640/EmailInjection.ql @@ -8,6 +8,7 @@ * @problem.severity error * @tags security * external/cwe/cwe-640 + * @precision high */ import go diff --git a/ql/src/experimental/CWE-640/EmailInjection.qll b/ql/src/Security/CWE-640/EmailInjection.qll similarity index 100% rename from ql/src/experimental/CWE-640/EmailInjection.qll rename to ql/src/Security/CWE-640/EmailInjection.qll diff --git a/ql/src/experimental/CWE-640/EmailInjectionCustomizations.qll b/ql/src/Security/CWE-640/EmailInjectionCustomizations.qll similarity index 100% rename from ql/src/experimental/CWE-640/EmailInjectionCustomizations.qll rename to ql/src/Security/CWE-640/EmailInjectionCustomizations.qll diff --git a/ql/test/experimental/CWE-640/EmailInjection.expected b/ql/test/experimental/CWE-640/EmailInjection.expected deleted file mode 100644 index 37a3af5ae75..00000000000 --- a/ql/test/experimental/CWE-640/EmailInjection.expected +++ /dev/null @@ -1,43 +0,0 @@ -edges -| email.go:24:10:24:17 | selection of Header : Header | email.go:27:56:27:67 | type conversion | -| email.go:34:21:34:31 | call to Referer : string | email.go:36:57:36:78 | type conversion | -| email.go:42:21:42:31 | call to Referer : string | email.go:45:3:45:7 | definition of write | -| email.go:51:21:51:31 | call to Referer : string | email.go:57:46:57:59 | untrustedInput | -| email.go:51:21:51:31 | call to Referer : string | email.go:58:52:58:65 | untrustedInput | -| email.go:63:21:63:31 | call to Referer : string | email.go:68:16:68:22 | content | -| email.go:73:21:73:31 | call to Referer : string | email.go:81:50:81:56 | content | -| email.go:73:21:73:31 | call to Referer : string | email.go:81:59:81:65 | content | -| email.go:73:21:73:31 | call to Referer : string | email.go:82:16:82:22 | content | -| email.go:87:21:87:31 | call to Referer : string | email.go:94:37:94:50 | untrustedInput | -| email.go:87:21:87:31 | call to Referer : string | email.go:98:16:98:23 | content2 | -nodes -| email.go:24:10:24:17 | selection of Header : Header | semmle.label | selection of Header : Header | -| email.go:27:56:27:67 | type conversion | semmle.label | type conversion | -| email.go:34:21:34:31 | call to Referer : string | semmle.label | call to Referer : string | -| email.go:36:57:36:78 | type conversion | semmle.label | type conversion | -| email.go:42:21:42:31 | call to Referer : string | semmle.label | call to Referer : string | -| email.go:45:3:45:7 | definition of write | semmle.label | definition of write | -| email.go:51:21:51:31 | call to Referer : string | semmle.label | call to Referer : string | -| email.go:57:46:57:59 | untrustedInput | semmle.label | untrustedInput | -| email.go:58:52:58:65 | untrustedInput | semmle.label | untrustedInput | -| email.go:63:21:63:31 | call to Referer : string | semmle.label | call to Referer : string | -| email.go:68:16:68:22 | content | semmle.label | content | -| email.go:73:21:73:31 | call to Referer : string | semmle.label | call to Referer : string | -| email.go:81:50:81:56 | content | semmle.label | content | -| email.go:81:59:81:65 | content | semmle.label | content | -| email.go:82:16:82:22 | content | semmle.label | content | -| email.go:87:21:87:31 | call to Referer : string | semmle.label | call to Referer : string | -| email.go:94:37:94:50 | untrustedInput | semmle.label | untrustedInput | -| email.go:98:16:98:23 | content2 | semmle.label | content2 | -#select -| email.go:27:56:27:67 | type conversion | email.go:24:10:24:17 | selection of Header : Header | email.go:27:56:27:67 | type conversion | Email content may contain $@. | email.go:24:10:24:17 | selection of Header | untrusted input | -| email.go:36:57:36:78 | type conversion | email.go:34:21:34:31 | call to Referer : string | email.go:36:57:36:78 | type conversion | Email content may contain $@. | email.go:34:21:34:31 | call to Referer | untrusted input | -| email.go:45:3:45:7 | definition of write | email.go:42:21:42:31 | call to Referer : string | email.go:45:3:45:7 | definition of write | Email content may contain $@. | email.go:42:21:42:31 | call to Referer | untrusted input | -| email.go:57:46:57:59 | untrustedInput | email.go:51:21:51:31 | call to Referer : string | email.go:57:46:57:59 | untrustedInput | Email content may contain $@. | email.go:51:21:51:31 | call to Referer | untrusted input | -| email.go:58:52:58:65 | untrustedInput | email.go:51:21:51:31 | call to Referer : string | email.go:58:52:58:65 | untrustedInput | Email content may contain $@. | email.go:51:21:51:31 | call to Referer | untrusted input | -| email.go:68:16:68:22 | content | email.go:63:21:63:31 | call to Referer : string | email.go:68:16:68:22 | content | Email content may contain $@. | email.go:63:21:63:31 | call to Referer | untrusted input | -| email.go:81:50:81:56 | content | email.go:73:21:73:31 | call to Referer : string | email.go:81:50:81:56 | content | Email content may contain $@. | email.go:73:21:73:31 | call to Referer | untrusted input | -| email.go:81:59:81:65 | content | email.go:73:21:73:31 | call to Referer : string | email.go:81:59:81:65 | content | Email content may contain $@. | email.go:73:21:73:31 | call to Referer | untrusted input | -| email.go:82:16:82:22 | content | email.go:73:21:73:31 | call to Referer : string | email.go:82:16:82:22 | content | Email content may contain $@. | email.go:73:21:73:31 | call to Referer | untrusted input | -| email.go:94:37:94:50 | untrustedInput | email.go:87:21:87:31 | call to Referer : string | email.go:94:37:94:50 | untrustedInput | Email content may contain $@. | email.go:87:21:87:31 | call to Referer | untrusted input | -| email.go:98:16:98:23 | content2 | email.go:87:21:87:31 | call to Referer : string | email.go:98:16:98:23 | content2 | Email content may contain $@. | email.go:87:21:87:31 | call to Referer | untrusted input | diff --git a/ql/test/experimental/CWE-640/EmailInjection.qlref b/ql/test/experimental/CWE-640/EmailInjection.qlref deleted file mode 100644 index a6d8abad1c9..00000000000 --- a/ql/test/experimental/CWE-640/EmailInjection.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/CWE-640/EmailInjection.ql \ No newline at end of file diff --git a/ql/test/query-tests/Security/CWE-640/EmailBad.go b/ql/test/query-tests/Security/CWE-640/EmailBad.go new file mode 100644 index 00000000000..aab8467b340 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-640/EmailBad.go @@ -0,0 +1,13 @@ +package main + +import ( + "net/http" + "net/smtp" +) + +func mail(w http.ResponseWriter, r *http.Request) { + host := r.Header.Get("Host") + token := backend.getUserSecretResetToken(email) + body := "Click to reset password: " + host + "/" + token + smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body)) +} diff --git a/ql/test/query-tests/Security/CWE-640/EmailGood.go b/ql/test/query-tests/Security/CWE-640/EmailGood.go new file mode 100644 index 00000000000..5a85e543540 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-640/EmailGood.go @@ -0,0 +1,13 @@ +package main + +import ( + "net/http" + "net/smtp" +) + +func mailGood(w http.ResponseWriter, r *http.Request) { + host := config["Host"] + token := backend.getUserSecretResetToken(email) + body := "Click to reset password: " + host + "/" + token + smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body)) +} diff --git a/ql/test/query-tests/Security/CWE-640/EmailInjection.expected b/ql/test/query-tests/Security/CWE-640/EmailInjection.expected new file mode 100644 index 00000000000..6e84a0466cd --- /dev/null +++ b/ql/test/query-tests/Security/CWE-640/EmailInjection.expected @@ -0,0 +1,43 @@ +edges +| EmailBad.go:9:10:9:17 | selection of Header : Header | EmailBad.go:12:56:12:67 | type conversion | +| main.go:29:21:29:31 | call to Referer : string | main.go:31:57:31:78 | type conversion | +| main.go:37:21:37:31 | call to Referer : string | main.go:40:3:40:7 | definition of write | +| main.go:46:21:46:31 | call to Referer : string | main.go:52:46:52:59 | untrustedInput | +| main.go:46:21:46:31 | call to Referer : string | main.go:53:52:53:65 | untrustedInput | +| main.go:58:21:58:31 | call to Referer : string | main.go:63:16:63:22 | content | +| main.go:68:21:68:31 | call to Referer : string | main.go:76:50:76:56 | content | +| main.go:68:21:68:31 | call to Referer : string | main.go:76:59:76:65 | content | +| main.go:68:21:68:31 | call to Referer : string | main.go:77:16:77:22 | content | +| main.go:82:21:82:31 | call to Referer : string | main.go:89:37:89:50 | untrustedInput | +| main.go:82:21:82:31 | call to Referer : string | main.go:93:16:93:23 | content2 | +nodes +| EmailBad.go:9:10:9:17 | selection of Header : Header | semmle.label | selection of Header : Header | +| EmailBad.go:12:56:12:67 | type conversion | semmle.label | type conversion | +| main.go:29:21:29:31 | call to Referer : string | semmle.label | call to Referer : string | +| main.go:31:57:31:78 | type conversion | semmle.label | type conversion | +| main.go:37:21:37:31 | call to Referer : string | semmle.label | call to Referer : string | +| main.go:40:3:40:7 | definition of write | semmle.label | definition of write | +| main.go:46:21:46:31 | call to Referer : string | semmle.label | call to Referer : string | +| 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 : string | semmle.label | call to Referer : string | +| main.go:63:16:63:22 | content | semmle.label | content | +| main.go:68:21:68:31 | call to Referer : string | semmle.label | call to Referer : string | +| 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 : string | semmle.label | call to Referer : string | +| main.go:89:37:89:50 | untrustedInput | semmle.label | untrustedInput | +| main.go:93:16:93:23 | content2 | semmle.label | content2 | +#select +| EmailBad.go:12:56:12:67 | type conversion | EmailBad.go:9:10:9:17 | selection of Header : 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 : string | 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 : string | 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 : string | 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 : string | 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 : string | 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 : string | 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 : string | 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 : string | 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 : string | 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 : string | main.go:93:16:93:23 | content2 | Email content may contain $@. | main.go:82:21:82:31 | call to Referer | untrusted input | diff --git a/ql/test/query-tests/Security/CWE-640/EmailInjection.qlref b/ql/test/query-tests/Security/CWE-640/EmailInjection.qlref new file mode 100644 index 00000000000..98862a2de38 --- /dev/null +++ b/ql/test/query-tests/Security/CWE-640/EmailInjection.qlref @@ -0,0 +1 @@ +Security/CWE-640/EmailInjection.ql \ No newline at end of file diff --git a/ql/test/experimental/CWE-640/go.mod b/ql/test/query-tests/Security/CWE-640/go.mod similarity index 100% rename from ql/test/experimental/CWE-640/go.mod rename to ql/test/query-tests/Security/CWE-640/go.mod diff --git a/ql/test/experimental/CWE-640/email.go b/ql/test/query-tests/Security/CWE-640/main.go similarity index 81% rename from ql/test/experimental/CWE-640/email.go rename to ql/test/query-tests/Security/CWE-640/main.go index 44a54b59fe7..c6685475a8d 100644 --- a/ql/test/experimental/CWE-640/email.go +++ b/ql/test/query-tests/Security/CWE-640/main.go @@ -3,6 +3,9 @@ package main //go:generate depstubber -vendor github.com/sendgrid/sendgrid-go/helpers/mail "" NewEmail,NewSingleEmail,NewContent,NewV3Mail,NewV3MailInit import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" "io" "log" "net/http" @@ -11,23 +14,15 @@ import ( sendgrid "github.com/sendgrid/sendgrid-go/helpers/mail" ) -// OK -func mailGood(w http.ResponseWriter, r *http.Request) { - host := config["Host"] - token := backend.getUserSecretResetToken(email) - body := "Click to reset password: " + host + "/" + token - smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body)) -} - -// Not OK -func mail(w http.ResponseWriter, r *http.Request) { - host := r.Header.Get("Host") - token := backend.getUserSecretResetToken(email) - body := "Click to reset password: " + host + "/" + token - smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(body)) -} - func main() { + var w http.ResponseWriter + var r *http.Request + + // Not OK + mail(w, r) + + // OK + mailGood(w, r) // Not OK http.HandleFunc("/ex0", func(w http.ResponseWriter, r *http.Request) { @@ -98,6 +93,19 @@ func main() { v.AddContent(content2) }) + // OK + http.HandleFunc("/ex6", func(w http.ResponseWriter, r *http.Request) { + untrustedInput := r.Referer() + + sha256 := sha256.New + appsecret := "appid" + hash := hmac.New(sha256, []byte(appsecret)) + hash.Write([]byte(untrustedInput)) + signature := base64.StdEncoding.EncodeToString(hash.Sum(nil)) + + smtp.SendMail("test.test", nil, "from@from.com", nil, []byte(signature)) + }) + log.Println(http.ListenAndServe(":80", nil)) } diff --git a/ql/test/experimental/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/LICENSE b/ql/test/query-tests/Security/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/LICENSE similarity index 100% rename from ql/test/experimental/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/LICENSE rename to ql/test/query-tests/Security/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/LICENSE diff --git a/ql/test/experimental/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/mail/stub.go b/ql/test/query-tests/Security/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/mail/stub.go similarity index 100% rename from ql/test/experimental/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/mail/stub.go rename to ql/test/query-tests/Security/CWE-640/vendor/github.com/sendgrid/sendgrid-go/helpers/mail/stub.go diff --git a/ql/test/experimental/CWE-640/vendor/modules.txt b/ql/test/query-tests/Security/CWE-640/vendor/modules.txt similarity index 100% rename from ql/test/experimental/CWE-640/vendor/modules.txt rename to ql/test/query-tests/Security/CWE-640/vendor/modules.txt