Server Side Request Forgery ('SSRF')
ID |
go.server_side_request_forgery |
Severity |
critical |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Channel |
Language |
Go |
Tags |
ASVS50:v1.3.6, ASVS50:v13.2.5, 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.
Severity when the destination server is fixed
The severity in the table above applies when the external input can reach the destination of the request. When the URL is built from a constant prefix that already fixes the scheme, the host and the port, the input lands after the authority and can only choose a path or a query string, so the request cannot be steered to another server. The detector recognizes this and reports the finding at low severity instead.
With BASE a constant "https://api.internal.example.com", a URL built as BASE + "/" + target
puts target in the path: it selects which endpoint of that fixed server is invoked, not which
server is invoked.
The finding is lowered rather than dropped, because the input still decides which endpoint of that server runs and may reach operations that were not meant to be callable this way. A path is also not always inert: if the fixed host answers with a redirect, an HTTP client that follows redirects can still end up requesting a location the caller did not choose.
The severity is left unchanged whenever the input can still affect the destination:
-
it reaches the scheme, the host or the port - for example
SCHEME + target + "/api","https://" + tenant + ".example.com/api"orBASE + ":" + port + "/api"; -
the prefix does not close the authority, as in
BASE + suffix, where the input can extend the host name into a different domain; -
the prefix cannot be resolved to a constant - a value imported from another module, a parameter default, or a name that is assigned again after its declaration, is not treated as fixed;
-
the prefix does not describe a host that a URL could carry, one with an empty label say. That is not a URL anyone writes, it is what is left when part of the expression could not be resolved, and the part that could not be resolved is the one under external control.
It is also left unchanged when the fixed host is itself the sensitive target, because there
choosing the path is the attack rather than a limitation of it: with a URL fixed to
http://169.254.169.254/latest, the path selects which instance credentials are read. This covers
cloud instance-metadata endpoints, loopback addresses, Kubernetes and cloud-internal service names,
and private or otherwise reserved address ranges. The check is purely lexical, so a public host name
that resolves to a private address at run time is not recognized.
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.