go.lang.security.injection.tainted-url-host.tainted-url-host

Author
unknown
Download Count*
License
User data flows into the host portion of this manually-constructed URL. This could allow an attacker to send data to their own server, potentially exposing sensitive data such as cookies or authorization information sent with this request. They could also probe internal servers or other resources that the server runnig this code can access. (This is called server-side request forgery, or SSRF.) Do not allow arbitrary hosts. Instead, create an allowlist for approved hosts hardcode the correct host.
Run Locally
Run in CI
Defintion
rules:
- id: tainted-url-host
languages:
- go
message: User data flows into the host portion of this manually-constructed URL.
This could allow an attacker to send data to their own server, potentially
exposing sensitive data such as cookies or authorization information sent
with this request. They could also probe internal servers or other
resources that the server runnig this code can access. (This is called
server-side request forgery, or SSRF.) Do not allow arbitrary hosts.
Instead, create an allowlist for approved hosts hardcode the correct host.
metadata:
cwe:
- "CWE-918: Server-Side Request Forgery (SSRF)"
owasp:
- A10:2021 - Server-Side Request Forgery (SSRF)
references:
- https://goteleport.com/blog/ssrf-attacks/
category: security
technology:
- go
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
confidence: MEDIUM
cwe2022-top25: true
cwe2021-top25: true
subcategory:
- vuln
impact: MEDIUM
likelihood: MEDIUM
mode: taint
pattern-sinks:
- patterns:
- pattern-either:
- patterns:
- pattern-either:
- pattern: fmt.Fprintf($F, "$URLSTR", ...)
- pattern: fmt.Sprintf("$URLSTR", ...)
- pattern: fmt.Printf("$URLSTR", ...)
- metavariable-regex:
metavariable: $URLSTR
regex: http(s?)://%(v|s|q).*
- patterns:
- pattern: '"$URLSTR" + ...'
- metavariable-regex:
metavariable: $URLSTR
regex: \w+://.*
pattern-sources:
- patterns:
- pattern-either:
- pattern: |
($REQUEST : *http.Request).$ANYTHING
- pattern: |
($REQUEST : http.Request).$ANYTHING
- metavariable-regex:
metavariable: $ANYTHING
regex: ^(BasicAuth|Body|Cookie|Cookies|Form|FormValue|GetBody|Host|MultipartReader|ParseForm|ParseMultipartForm|PostForm|PostFormValue|Referer|RequestURI|Trailer|TransferEncoding|UserAgent|URL)$
severity: WARNING
Examples
tainted-url-host.go
package main
import (
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)
func handlerIndex(w http.ResponseWriter, r *http.Request) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
if r.Method == "POST" && r.URL.Path == "/api" {
// ruleid: tainted-url-host
url := fmt.Sprintf("https://%v/api", r.URL.Query().Get("proxy"))
resp, err := client.Post(url, "application/json", r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
w.WriteHeader(500)
return
}
w.Write([]byte(fmt.Sprintf("{\"host\":\"%v\"}", r.URL.Query().Get("proxy"))))
return
} else {
proxy := r.URL.Query()["proxy"]
secure := r.URL.Query()["secure"]
if (secure) {
// ruleid: tainted-url-host
url := fmt.Sprintf("https://%s", proxy)
} else {
// ruleid: tainted-url-host
url := fmt.Sprintf("http://%q", proxy)
}
resp, err := client.Post(url, "application/json", r.Body)
}
}
func handlerOther(w http.ResponseWriter, r *http.Request) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
if r.Method == "POST" && r.URL.Path == "/api" {
// ruleid: tainted-url-host
url := fmt.Printf("https://%v/api", r.URL.Query().Get("proxy"))
resp, err := client.Post(url, "application/json", r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
w.WriteHeader(500)
return
}
w.Write([]byte(fmt.Sprintf("{\"host\":\"%v\"}", r.URL.Query().Get("proxy"))))
return
} else {
proxy := r.URL.Query()["proxy"]
secure := r.URL.Query()["secure"]
if (secure) {
// ruleid: tainted-url-host
fmt.Fprintf(w, "https://%s", proxy)
} else {
// ruleid: tainted-url-host
fmt.Fprintf(w, "http://%q", proxy)
}
resp, err := client.Post(url, "application/json", r.Body)
}
}
func handlerOk(w http.ResponseWriter, r *http.Request) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
if r.Method == "POST" && r.URL.Path == "/api" {
// ok: tainted-url-host
url := fmt.Printf("https://example.com/%v", r.URL.Query().Get("proxy"))
resp, err := client.Post(url, "application/json", r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
w.WriteHeader(500)
return
}
w.Write([]byte(fmt.Sprintf("{\"host\":\"%v\"}", r.URL.Query().Get("proxy"))))
return
} else {
proxy := r.URL.Query()["proxy"]
secure := r.URL.Query()["secure"]
if (secure) {
// ok: tainted-url-host
url := fmt.Sprintf("https://example.com/%s", proxy)
} else {
// ok: tainted-url-host
fmt.Fprintf(w, "http://example.com/%q", proxy)
}
resp, err := client.Post(url, "application/json", r.Body)
}
}
func newRedirectServer(addr string, rootPath string) *http.Server {
return &http.Server{
Addr: addr,
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// ruleid: tainted-url-host
target := "https://" + req.Host
if rootPath != "" {
target += "/" + strings.TrimRight(strings.TrimLeft(rootPath, "/"), "/")
}
target += req.URL.Path
if len(req.URL.RawQuery) > 0 {
target += "?" + req.URL.RawQuery
}
http.Redirect(w, req, target, http.StatusTemporaryRedirect)
}),
}
}
func main() {
http.HandleFunc("/", handlerIndex)
http.HandleFunc("/other", handleOther)
http.HandleFunc("/ok", handleOk)
http.ListenAndServe(":8888", nil)
}
Short Link: https://sg.run/5DjW