Skip to content

Instantly share code, notes, and snippets.

@lorennorman
Last active May 1, 2026 19:21
Show Gist options
  • Select an option

  • Save lorennorman/aaa1848d4dec52aa1cf60549717c396e to your computer and use it in GitHub Desktop.

Select an option

Save lorennorman/aaa1848d4dec52aa1cf60549717c396e to your computer and use it in GitHub Desktop.
SSRF & TOCTOU Attack Explainer

SSRF & TOCTOU Attack Explainer

I learned something today and wanted to share.

Special IP Addresses

There are ranges of IP addresses that mean special things, some refer to:

  • yourself (localhost)
  • other machines on your local network (like other computers you own inside the same datacenter)
  • special networking services (provided by AWS or whomever)

DNS: Normal & Tricky

  • DNS turns domain names into IP addresses
  • normally, DNS is highly static: always responds the same and caches hard to keep the web fast and efficient
  • tricky DNS resolvers break this norm, resolving in different ways with short TTLs

rbndr.us is an example of a tricky one I just learned about. It uses a DNS resolver that automatically oscillates its IP address resolution between the subdomains you give it. So if you resolve http://a9fea9fe.01010101.rbndr.us it will return one of 2 IP address (specified by a9fea9fe and 01010101).

This is easily verified with the dig utility:

# dig +short DOMAIN - quick ip address resolution
$ dig +short a9fea9fe.01010101.rbndr.us
1.1.1.1 # benign IP address
$ dig +short a9fea9fe.01010101.rbndr.us
169.254.169.254 # dangerous IP address

HTTP hides DNS

When we make an HTTP requests to a domain name, it performs a DNS resolution to an IP address automatically and invisibly.

Webhooks

Executing webhooks for end-users is dangerous because:

  • you make an http request (these have many gotchas)
  • ...from inside your infrastructure (your user/server can see privileged information)
  • ...to a user-provided address to request (never trust the user)

So when a user gives you a URI to post to, naturally you want to go ahead and resolve that in advance and check what comes back against a list of bad addresses. Easy enough, what's the problem?

Time-of-Check-vs-Time-of-Use Attack:

If you verify DNS points to a safe IP address as a separate process from when you make the HTTP request, you can be attacked by nefarious DNS resolvers, which are trivially used by your end-users.

Code that looked safe to me:

  def send_request_safely url, options
      unless validate_url(url) 
        raise "naw, dawg"
      end
      # ...milliseconds later...
      response = HTTParty.get(url, options)
      
      show_to_user(response)
  end

Seems fine, right? We validate the final destination before we make the request on every request.

Unsafe code explained:

  def send_request_safely url, options
	  # resolve DNS and check block list
      unless validate_url(url) 
        raise "naw, dawg"
      end
      # ...milliseconds later...
      response = HTTParty.get(url, options) # resolves DNS all over again... oops
      
      show_to_user(response) # pwned!
  end

Solution

The solution is to make sure the HTTP request doesn't do a separate DNS resolution from the checked one. Depends on which programming language, built-in utilities, 3rd party libraries, etc that you're using.

In the case of Ruby, there is the ssrf_filter gem, which handles all of this for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment