Mitigation of server-side request forgery (SSRF) attacks
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.
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.