Server-side request forgery (SSRF)
Need
Mitigation of server-side request forgery (SSRF) attacks
Context
- Usage of Go 1.16 for developing high-performance and efficient applications
- Usage of gin-gonic/gin for building web applications in Go
- Usage of net/http for building HTTP servers in a Node.js application
- Usage of io/ioutil for reading and writing files in Node.js
Description
Non compliant code
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"io/ioutil"
)
func main() {
router := gin.Default()
router.GET("/proxy", func(c *gin.Context) {
target := c.Query("target")
resp, err := http.Get(target)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error when making the request"})
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error when reading the response"})
return
}
c.String(http.StatusOK, string(body))
})
router.Run()
}
This is a simple Go server application using the Gin framework. It has a "/proxy" endpoint that takes a "target" URL as a query parameter and sends a GET request to that URL.
The vulnerability lies in the /proxy
route. In this route, the application takes a user-supplied URL (from the target
query parameter) and makes a GET request to this URL using the http.Get(target)
function. This is a classic example of a Server-Side Request Forgery (SSRF) vulnerability because the application server can be tricked into making arbitrary requests to any domain.
An attacker can exploit this vulnerability by sending a request to the /proxy
endpoint with a target
parameter pointing to an internal resource (e.g., http://localhost/admin) or to an external resource under their control. The server will then send a GET request to this URL, potentially exposing sensitive information or interacting with other services in a way that was not intended by the application developers.
Steps
- Validate the 'target' parameter to ensure it is a valid URL.
- Implement a whitelist of allowed domains that the application can make requests to.
- Use a DNS resolution library to resolve the domain from the 'target' parameter and compare it against the whitelist.
- If the domain is not in the whitelist, reject the request and return an appropriate error message.
- Consider implementing rate limiting or request throttling to prevent abuse of the proxy endpoint.
- Implement proper error handling and logging to track any potential SSRF attempts.
Compliant code
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"io/ioutil"
"net/url"
"strings"
"errors"
)
var allowedDomains = []string{
"example.com",
"example.org",
}
func main() {
router := gin.Default()
router.GET("/proxy", func(c *gin.Context) {
target := c.Query("target")
err := validateURL(target)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid target URL"})
return
}
resp, err := http.Get(target)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error when making the request"})
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error when reading the response"})
return
}
c.String(http.StatusOK, string(body))
})
router.Run()
}
func validateURL(target string) error {
u, err := url.Parse(target)
if err != nil {
return err
}
for _, domain := range allowedDomains {
if strings.HasSuffix(u.Hostname(), domain) {
return nil
}
}
return errors.New("Invalid domain")
}
The updated code includes a function validateURL
that validates the 'target' parameter to ensure it is a valid URL and that the domain is in the whitelist of allowed domains.
The allowedDomains
variable is a slice of strings that contains the list of allowed domains. This list can be modified to include the domains that the application should be allowed to make requests to.
The validateURL
function parses the 'target' parameter using the url.Parse
function from the net/url
package. If the parsing is successful, it checks if the domain (obtained using u.Hostname()
) is in the allowedDomains
list. If the domain is in the list, the function returns nil
indicating that the URL is valid. If the domain is not in the list, the function returns an error.
In the main function, before making the HTTP request, the 'target' parameter is validated using the validateURL
function. If the function returns an error, a http.StatusBadRequest
is returned with an error message "Invalid target URL". If the function does not return an error, the HTTP request is made as before.
This updated code helps to mitigate the Server-side Request Forgery (SSRF) vulnerability by ensuring that the application can only make requests to a whitelist of allowed domains.