OWASP Tip: A10:2021 – Server-Side Request Forgery (SSRF)

Our final entry in the OWASP Top 10 series - be aware of what your servers can access!

OWASP Tip: A10:2021 – Server-Side Request Forgery (SSRF)

Greetings friends! We’ve made it! This is the last week of our OWASP Top 10 series, and it has been fun taking this journey with all of you. If you’ve missed any of the previous Top 10, you can find them all linked here. To finish off the series, I’m going to talk about Server-Side Request Forgery (SSRF) and why it was included in the Top 10. It’s a topic I don’t see talked about much in the Laravel space, but as always, it’s something we need to be aware of.

You may have seen an email last week about a new Substack Chat that has started for Laravel Security in Depth. It’s a new feature they’ve added to the Android and iOS app1, which allows us to chat directly and discuss different topics. I’ve started a discussion about authentication authorisation2 and another one for suggestions on topics in our emails. Go check it out if you’re interested! Our main content will still be via these weekly emails, so if a chat isn’t your thing, all good. 👍

💡 Need to meet compliance requirements with an annual security audit and penetration test before your certification renews? I’m currently booking out April and May, so reach out now to book your audit! 🕵️

Looking to learn more?
Security Tip #12: Rate Limited Logins
▶️ In Depth #4: Guessing Placeholders

A10:2021 – Server-Side Request Forgery (SSRF)

Server-Side Request Forgery (SSRF) is an interesting one. OWASP report it as having “a relatively low incidence rate with above average testing coverage and above-average Exploit and Impact potential ratings”. The only reason it was included in the Top 10 is due to the community survey highlighting it as a critical risk, but since the whole point of the community survey is to highlight risks that may be overlooked, we can’t treat it any different to the rest of the Top 10.

The way SSRF works is incredibly simple:

  1. User submits a URL to the App
  2. App makes a request to the URL and records the response
  3. App returns the response to the User

While this may sound like a normal workflow in some apps, the risk is that the App may have access to sensitive information or be able to perform restricted actions that the User should not have access to. The User cannot directly make these requests, but since the App is the one making the request, it potentially has access to private networks, internal APIs, inherited credentials, and can sometimes even bypass firewalls and IP blocks. An attacker can exploit this access by getting the App to make the requests, and simply waiting for the App to return the data they are looking for.

If you need to allow users to specify URLs and return the output form those URLs, then my recommendations are:

  1. Limit the URLs to HTTPS-only.
    (Many internal APIs are HTTP-only.)
  2. Check the requested domain name resolves on public DNS.
    (Laravel’s active_url validation rule should do this for you.)
  3. Specifically block known internal endpoints.
    (Blocklists are incredibly useful for SSRF protection.)

Metadata APIs

It may sound like SSRF is something you only need to worry about when you’ve got multiple servers and networks in your app stack, but it’s worth remembering that hosting providers usually have internal API endpoints you can call from your servers and apps to retrieve metadata about the environment.

For example, I use DigitalOcean for my servers, who have a Metadata API. If I log into the app powering the LSID vulnerable web app, I can call that endpoint in Tinker.

This is the response from the API (with a bunch of the noise removed):

larasec@fili:~/larasec.evilhacker.dev$ php artisan tinker
Psy Shell v0.11.8 (PHP 8.0.21 — cli) by Justin Hileman
>>> Http::get('http://169.254.169.254/metadata/v1.json')->json();
=> [
     "droplet_id" => ...,
     "hostname" => "fili",
     "vendor_data" => """
     ... REMOVED ...
       """,
     "public_keys" => [
       "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC.../1y535WtDxQ== worker@forge.laravel.com",
     ],
     "region" => "sgp1",
     "interfaces" => [
       "private" => [
         [
           "ipv4" => [
             "ip_address" => "...",
             "netmask" => "...",
             "gateway" => "0.0.0.0",
           ],
           "mac" => "...",
           "type" => "private",
         ],
       ],
       "public" => [
         [
           "ipv4" => [
             "ip_address" => "143.198.203.203",
             "netmask" => "255.255.240.0",
             "gateway" => "143.198.192.1",
           ],
           "ipv6" => [
             "ip_address" => "2400:6180:0000:00D0:0000:0000:1316:6001",
             "cidr" => 64,
             "gateway" => "...",
           ],
           "anchor_ipv4" => [
             "ip_address" => "...",
             "netmask" => "255.255.0.0",
             "gateway" => "...",
           ],
           "mac" => "...",
           "type" => "public",
         ],
       ],
     ],
     "floating_ip" => [
       "ipv4" => [
         "active" => false,
       ],
     ],
     "reserved_ip" => [
       "ipv4" => [
         "active" => false,
       ],
     ],
     "dns" => [
       "nameservers" => [
         "67.207.67.2",
         "67.207.67.3",
       ],
     ],
     "tags" => [
       "monitoring",
       "firewall-ssh",
       "firewall-web",
     ],
     "features" => [
       "dhcp_enabled" => false,
     ],
     "modify_index" => 125692097,
     "dotty_keys" => [
       "{"os_user":"root","ssh_key":"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXN...pOuU=","actor_email":"stephen@rees-carter.net","ttl":180}",
     ],
     "dotty_status" => "running",
     "ssh_info" => [
       "port" => 22,
     ],
   ]

That is a significant amount of data!! It may not appear directly sensitive, and I’m sure hosting providers like DO try to limit the sensitive data in there, but it’s still incredibly useful.

In the output above you’ll note the IPv4 of the server is 143.198.203.203. With this IP address you can bypass a cloud WAF/Firewall/CDN (i.e. Cloudflare, Akamai, etc) and make requests directly at the server. For some applications this will bypass security controls, such as rate limits and geolocation blocks, and effectively weakens the security of the site!

Other Schemes…

I also need to point out that SSRF doesn’t just refer to web APIs.

If you’re not properly validating the user-submitted URLs your application is making requests to, you could be letting some of these through:

file:///etc/passwd
dict://
sftp://
tftp://
ldap://
gopher://

My recommendation here is to always use a robust HTTP client like Laravel’s HTTP Client or the Guzzle HTTP Client, which should only allow http:// and https:// by default. This won’t stop internal API requests, but it will prevent other protocols being used for malicious purposes.


  1. There is apparently a web version coming soon. 🤞

  2. I somehow put the wrong word in the topic and can’t edit it! 😱