Server Side Request Forgery ('SSRF')
ID |
go.server_side_request_forgery |
Severity |
critical |
Resource |
Channel |
Language |
Go |
Tags |
CWE:918, NIST.SP.800-53, OWASP:2021:A10, PCI-DSS:6.5.8 |
Description
Improper validation of external input used to retrieve the content of a URL ('SSRF').
SSRF vulnerabilities arise when an application accepts user input to create or control URLs or networking requests without appropriate validation or restriction. This can allow attackers to craft malicious requests to sensitive internal endpoints or external services on behalf of the server.
Rationale
Similar to other injection attacks, an SSRF vulnerability allows an attacker to manipulate the URLs used by a server-side application, to point to unexpected internal or external resources.
By carefully selecting the URLs, the attacker can read or update internal resources, such as cloud metadata, internal services, or databases. In addition, SSRF can bypass network access controls like firewalls and VPNs.
An SSRF vulnerability can be exploited by threat actors for network reconnaissance, data exfiltration, denial of service or pivot attacks to other systems.
For instance, consider a Golang application that fetches resources based on user-provided URLs:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func handler(w http.ResponseWriter, req *http.Request) {
// Vulnerable: user input directly used in HTTP request
url := req.URL.Query().Get("url")
resp, err := http.Get(url)
if err != nil {
http.Error(w, "Failed to fetch URL", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
http.Error(w, "Error reading response", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Response: %s", body)
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
In this example, the application uses user-controlled input to make an HTTP request, allowing an attacker to target internal services by providing a crafted URL.
Remediation
To mitigate SSRF, validate and restrict the URLs that the application can access. Use allowlists to define acceptable domains and ports, and avoid accessing sensitive internal services based on external input.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
func handler(w http.ResponseWriter, req *http.Request) {
userURL := req.URL.Query().Get("url")
// Validate and restrict URL
if !isValidURL(userURL) {
http.Error(w, "Invalid URL", http.StatusBadRequest)
return
}
resp, err := http.Get(userURL)
if err != nil {
http.Error(w, "Failed to fetch URL", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
http.Error(w, "Error reading response", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Response: %s", body)
}
// Simple validation function using a domain allowlist
func isValidURL(rawURL string) bool {
parsedURL, err := url.Parse(rawURL)
if err != nil || !strings.HasPrefix(parsedURL.Scheme, "http") {
return false
}
allowedDomains := []string{"example.com", "api.example.com"}
for _, domain := range allowedDomains {
if strings.HasSuffix(parsedURL.Hostname(), domain) {
return true
}
}
return false
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
In this secure example, the function isValidURL
checks if the URL belongs to an allowed set of domains, ensuring that only appropriate and safe requests are sent, protecting against SSRF attacks.
Configuration
The detector has the following configurable parameters:
-
sources
, that indicates the source kinds to check. -
neutralizations
, that indicates the neutralization kinds to check.
Unless you need to change the default behavior, you typically do not need to configure this detector.
References
-
CWE-918 : Server-Side Request Forgery (SSRF).
-
OWASP - Top 10 2021 Category A10 : Server-Side Request Forgery (SSRF).
-
Server-Side Request Forgery Prevention Cheat Sheet, in OWASP Cheat Sheet Series.