SSRF in real life

Mickael Jeanroy
Alan Product and Technical Blog
6 min readSep 22, 2022
“You shall not pass”

Introduction

At Alan, we are continuously improving our product to delight our members. One downside is that increasing our codebase size exposes us to security issues and vulnerabilities.

Let me tell you about a story that happened recently.

Discovery

A few weeks ago, I was working on a feature, and I tried to understand the impact of my change on the codebase.

Basically, I wanted to check all usages of a redirect_uri parameter: I used my favorite IDE and started to look for URI parameters in our frontend codebase when I found something weird.

A feature developed a few months ago allowed members to share links pointing to documents with external users (it was used to send prescriptions to a pharmacist): this link was then sent to our backend to download the file, and then we sent this document by email.

You can see the flow here for a better understanding:

File sharing basic flow

Let me give a little explanation:

  1. The user opens a webpage used to share a file, and the document location (not reachable from the Internet) is specified as a query parameter.
  2. Some JavaScript code calls our backend API with this URI parameter.
  3. The document is downloaded from one of our S3 buckets, not reachable from the Internet.
  4. The document is then sent by email to an external user.

In this kind of scenario, one should always wonder: “Is a Server Side Request Forgery possible?” (spoiler alert: it was).

Server Side Request Forgery (SSRF) is one of the most obscure and misleading web vulnerabilities but also one of the most dangerous, as we can see in the top 10 OWASP rankings in 2021.

It allows an attacker to induce the server-side application to make requests to an unintended location (for exemple: a server owned by the attacker or an endpoint only reachable from the internal network).

An attacker could also scan the entire internal network to find open ports in various locations. For example, if your database exposes a REST API, an attacker could use this vulnerability to exfiltrate user data.

Let’s adapt the previous schema to exploit this kind of vulnerability:

SSRF Attack

Here is a short explanation of the attack:

  1. The attacker opens a webpage used to share a file and replaces the document URI to point to a server he owns.
  2. Some JavaScript code calls our backend API with this URI parameter.
  3. Our server downloads the file from the attacker’s server.
  4. The document is then sent by email.

This vulnerability can become critical because the attacker could access the entire internal network. Intuitively, SSRF vulnerabilities occur because a server will willingly perform an HTTP operation with its own privilege, and from its own context deep inside the infrastructure on behalf of a client, and this may allow the client to access a resource that should not be available to them.

For example:

  • What if the attacker could send a request from our server to an internal endpoint hosted on 127.0.0.1? It can be very dangerous if you expose an administration endpoint without authentication only reachable from localhost!
  • Even worse, what if the attacker interacts with another back-end system that is not directly reachable by users? If you’re hosted on AWS, an attacker could access your entire AWS metadata hosted on 169.254.169.254 (an endpoint reachable from your EC2 instance to retrieve instance metadata, see the documentation here)!

Exploitation

So here it is:

  • We have a link sent to our backend.
  • This link corresponds to a file that is downloaded on our server.
  • The document is then sent by email.

The first thing I tried was to change the link to an URL I own, said differently I changed https://[legit_host]/path/document.pdf to https://[evil_host].

Unfortunately, it did not work, so I thought about it a bit more and tried other payloads:

  • I tried to change the URI scheme to use file:// - remember, an URI is not necessarily an HTTP URI - but it did not work: only http(s):// schemes were allowed.
  • Changing the URL path failed as well: the document does not exist obviously.
  • But: changing the host while keeping the URL path worked! I was able to call my server with the following URL https://[evil_host]/path/document.pdf 🎉

To make it clear, I now am able to do this kind of attack:

SSRF Attack

But, as you guess, the impact is very low: since I cannot change the path, I can’t call internal endpoints unless it responds on this specific path (which is unlikely).

But… Wait a minute, you can use redirects in HTTP, right?

Let’s dig deeper

At this point, I can make our backend trigger a request on a host I own, but I cannot change the path.

But:

  • What if my server responds with a redirect to an internal endpoint, such as http://169.254.169.254?
  • Usually, by default, HTTP libraries follow redirects, so chances are it will work!

Let’s update the diagram above to explain the attack:

SSRF Attack using redirect

The attack becomes a bit more complex:

  1. The attacker opens a webpage used to share a file and replaces the document URI hostname to point to a server he owns.
  2. Some JavaScript code calls our backend API with this URI parameter.
  3. Our server calls the attacker’s server.
  4. The server returns an HTTP 301 with a Location header set to “http://169.254.169.254”
  5. Our server receives the response, follows the redirection, and calls the URL set in the Location header: http://169.254.169.254
  6. The server sends the document containing the result of the call by email.

And it worked, I was able to get our full AWS metadata by email 🙂🎉

Correction & Conclusion

As you can see, the impact of an SSRF vulnerability can be huge, here are a few pieces of advice to keep your application secure:

  • Before calling an HTTP URL, always validate the hostname. I strongly suggest using an authorization list instead of a deny list, as there are a lot of ways to bypass such restrictions (a custom hostname pointing to an internal IP is not so hard using nip.io 🙂).
  • By default, your HTTP client should not follow redirects, as it could be used to bypass internal checks implemented in your business logic.

In our case, the feature was not used and has been removed, this was the easiest fix in my life! It was also a good reminder that we should not be shy to remove code, it’s usually a good idea 😁

As follow-up actions:

  • I looked for unexpected usage of this feature, and fortunately, I did not find anything suspicious.
  • I also implemented a linter to ensure we don’t use URL/URI parameters in an unexpected way (as one of our engineering principles is “We make others fall into the pit of success”). In a few weeks’ time, several use cases were flagged, and this allowed us to engage with developers and raise their awareness of the issue.

If you are interested in this kind of investigation, and if you like to dig in-depth, keep in mind that we are still hiring!

--

--