Web applications are no longer just assets to a company — they’re an organization’s identity, playing a major role in how customers, clients, and users see a brand. Due to this importance, web apps have also become a primary target for attack.

Over the years, these applications have grown more complex and bigger in size. Meanwhile, attackers have gotten more skillful. This has created greater opportunities for malicious actors to exploit potential vulnerabilities in web applications.

With the recent release of the 2021 Open Web Application Security Project (OWASP) top 10, we’re taking a deep dives into some of the new items added to the list. So far, we’ve covered injection and vulnerable and outdated components. In this post, we’ll focus on server-side request forgery (SSRF), which comes in at number 10 on the updated list.

SSRF attacks present a range of risks, from potentially stealing sensitive information from the application to bringing the entire web application down. These attacks target systems that are located behind firewalls and restrict access from non-trusted networks. Protecting your application from such attacks is vitally important. Here, we'll talk about the different types of SSRF attacks and go over some mitigation techniques

What is server-side request forgery (SSRF)?

SSRF allows an attacker to force the server-side application into making arbitrary web requests to an unintended domain. This can result in the server making connections to internal-only services or arbitrary external systems.

A successful SSRF attack can result in unauthorized actions or access to data within the organization, either in the vulnerable application itself or on other back-end systems that the application can communicate with. In some situations, the SSRF vulnerability may even allow an attacker to perform arbitrary command execution. This can result in:

  • Information exposure
  • Internal reconnaissance
  • Denial-of-Service (DoS) attack
  • Remote code execution (RCE)

In general, SSRF attacks are made possible by a lack of user input validation in the web application. Without strict validation, the attacker can alter parameters that control what gets executed server-side, e.g. potentially malicious commands or establishing HTTP connections to arbitrary systems. Vulnerabilities will arise when the web application is unable to identify and validate requests from trusted applications or when the web application can send requests to any external IP address or domain

A closer look

Consider a scenario where the target web application provides functionality for importing, publishing, or reading data using a URL query parameter. The user can control the source of the data accessed by changing the value of the query parameter, which modifies the web request made by the server.

Once the manipulated request is received by the server, it will attempt to read the data by making a request to the user-supplied URL. If the web application lacks sufficient validation of user supplied data (in this case the source URL), then an attacker could potentially supply a URL that returns information from services that aren’t directly exposed publicly.

In some cases, the application server is able to interact with other back-end systems that are not directly reachable by users. Such systems often have private IP addresses and are designed not to be accessed publicly. Internal back-end systems may contain sensitive functionality that can be accessed without authentication by anyone who is able to interact with the systems.

A common example of this is cloud server metadata. Cloud services like Azure and AWS provide a representational state transfer (REST) service for querying metadata about the service itself. For example, AWS provides Instance Metadata Service (IMDS), which is used for querying information about an Amazon EC2 instance. This service is not publicly accessible and can only be accessed from the EC2 instance itself, by making a local HTTP request on http://169.254.169.254/.

The reason for this is that applications can sometimes hold important configuration files and authentication keys in these metadata directories. Endpoints that expose sensitive metadata like this are prime targets for attackers who wish to exploit SSRF vulnerabilities in applications with weak input validation.

Other examples of target data include Database HTTP interfaces such as NoSQL and MongoDB, as well as Internal REST interfaces and standard files structures.

Types of SSRF

Based on how a server responds to the request, SSRF can be divided into two types.

Basic SSRF: This when data from the malicious, forced back-end request is reflected in the application front-end. A hacker would use Basic SSRF when they want to exfiltrate data from the server directly or want to access unauthorized features.

Blind SSRF: As the name describes, with this type of SSRF attack, the application is forced to make a back-end HTTP request to a malicious domain. In this type of SSRF, the attacker doesn't get data back from the server directly. The response from the back-end request triggers an action on the target without getting reflected in the application front-end. Hackers use this type of SSRF when they want to make some changes using the victim server.

Typical attack approach

Next, we’ll look at a sample attack that an attacker may use to test for SSRF vulnerabilities that are exposed when trying to acquire metadata from an Azure instance.

<AttackConfig>
      <Id>SSRF_13</Id>
      <Description><![CDATA[Azure Metadata]]></Description>
      <CustomParameterList>
        <CustomParameter>
          <Name>AttackString</Name>
          <Value>
              <![CDATA[http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15]]>
          </Value>
        </CustomParameter>
        <CustomParameter>
          <name>vulnregex</name>
          <value>"(azEnvironment|osType|resourceId|vmSize)":\"</value>
        </CustomParameter>
      </CustomParameterList>
    </AttackConfig>

Upon identifying an injection point in, for example, a post parameter sent in the body of the request, the attacker may attempt to inject the value wrapped in CDATA — i.e. http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15 in our example.

That might look something like this:

POST /users/search HTTP/1.0 Content-Type: application/x-www-form-urlencoded

searchApi=http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15

The injected value contains the domain that retrieves metadata about the Azure instance that the web application is served on. Assuming that the web application is vulnerable to SSRF, no input validation will be performed to reject this malicious domain, and the web application will arbitrarily make a HTTP request that should result in Azure metadata being reflected in the web response.

If the application returns a valid response, the attacker could then search the web response for the value identified by “vulnregex” above. Matches should occur for information corresponding to the Azure instance — i.e. environment name, operating system, available resources or its storage size. This is a strong indication that the forged request was successful and therefore the application is vulnerable to SSRF attacks.

Validation

The above attack can be validated by attempting to visualize the information yourself. You can do this by navigating to the location on the application where a query parameter is being passed in the URL and injecting the value:

http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15

When the forged request is submitted, you should look to see if any unexpected, sensitive information is returned in the response. In this case, since we are injecting an instance metadata domain, relevant information like operating system and storage size should be returned. If it is, this provides confirmation that the application is vulnerable to SSRF. An attacker could leverage this further to access and possibly even alter information in the metadata directory for that instance.

Sample vulnerable code

public String documentPreview(HttpServletRequest httpRequest,
                              Model model)
{
  String queryStringParams = httpRequest.getQueryString();
  String queryString = 
		StringUtils.substringAfter(queryStringParams, "url=");
​
  if (StringUtils.isBlank(queryString)) {
    log.error("Missed 'url' query param");
    return "preview";
  }
​
  try {
    DownloadFileResponse downloadFileResponse = 											storageService.load(queryString);
    
    model.addAttribute("image", new String(Base64.getEncoder().encode(
		    IOUtils.toByteArray(downloadFileResponse.getContent()))
    ));
  }
  catch (Exception e) {
    // Exception handling here.
}
​
  return "preview";
}

HttpGet httpGet = new HttpGet(url);

This example code has been created to upload images to an application and render them. However, it is vulnerable to SSRF attacks that will allow the attacker to make arbitrary requests to internal systems, such as metadata information.

The documentPreview() method is used for rendering an uploaded image file. This works by extracting a pre-signed image location URL passed via the “url=” parameter and assigns it to the variable named queryString. This variable is then passed into a storageService method which loads the image from where it is stored.

The load()method will invoke the HttpGet() function in order to retrieve the image. However, without proper input validation on the request parameter “url=”, the httpGet()method will perform arbitrary get requests on anything malicious that is input via that parameter.

Sample fixed code and remediation

The standard approach for preventing SSRF attacks can include denylist- and allowlist-based input validation for the URL.

Denylisting can include blocking hostnames like 127.0.0.1 or 169.254.169.254 so that the attacker cannot actively access internal information by injecting these parameters. This is useful when the application is required to send requests to external IP addresses or domains.

When the application is only required to send requests to trusted/identified applications, allowlist validation is available. This means the web application will only accept certain values as valid parameters. For example, you could implement embedded credentials in a URL before the hostname using the @ character so that the application can only access directories after the provided hostname.

To remediate our above example, the approach would be to implement some allowlist validation, as we only need to load images from a trusted single file storage service.

You could use regex to see if the parameter matches the trusted file storage hostname:

//Regex validation for a data having a simple format
if(Pattern.matches("http:\/\/trustedimages.com.*", queryString)){
    //Continue the processing because the input data is valid
	HttpGet httpGet = new HttpGet(url);
}else{
    //Stop the processing and reject the request
}

After the above code is implemented, only parameters beginning with http://trustedimages.com/ will be able to be sent to the httpGet()method and will prevent attackers from accessing hostnames outside of that domain.

Fighting a new contender

Don’t let the No. 10 spot fool you — SSRF is a serious threat that more than deserves its recognition in this year’s OWASP Top 10 list. In fact, 2021 is SSRF’s first year on the OWASP list, and security pros should expect to encounter this threat more and more in the coming years. But if you’re effectively testing your applications and remediating issues quickly and correctly, you’ll be prepared to spot and resolve SSRF vulnerabilities before an attacker exploits them.

NEVER MISS A BLOG

Get the latest stories, expertise, and news about security today.