16

The application lets users specify a URL for their profile picture. It fetches the data from the URL and saves it on the server. However, the app is vulnerable to server-side request forgery (SSRF) - you can specify URLs like file:///etc/passwd and also access local HTTP services like http://localhost:8080/.

What's the best way to fix this? Some ideas I had:

  • Restrict the URL protocol to HTTP and HTTPS.
  • DNS lookup the host name and check it's not a local address.
  • Disable redirects.

This seems a good start - although there's a TOCTOU issue with the DNS lookup (may be ok in practice, as local DNS will cache).

An alternative idea I had was to place a proxy in a restricted network segment, where it can only see the Internet, and send requests through that.

I did research this online, but there's not much information. This article is pretty good.

Anders
  • 65,052
  • 24
  • 180
  • 218
paj28
  • 32,906
  • 8
  • 93
  • 130
  • 2
    Using a segmented proxy server is a good idea. I answered a similar question [here](http://security.stackexchange.com/a/114999/8340). DNS caching can be circumvented by building the request manually (i.e. explicitly send the request to the resolved IP address and set `host` header/the host used for SNI manually). – SilverlightFox May 18 '16 at 18:26
  • Thanks @SilverlightFox. Any tip on how to explicity send a request to specific resolved IP? I couldn't see how with java.net.URL – paj28 May 18 '16 at 20:30
  • See [this answer](http://stackoverflow.com/q/9096987/413180) - you can set the IP address as the host, then manually set the Host header. Of course for HTTPS connections you'll have to check that the API automatically uses your set Host header for the SNI sent during handshaking. – SilverlightFox May 19 '16 at 10:50
  • Would a whitelist/blacklist solution work, or is it too restrictive? For example, you could whitelist several known image sharing websites. Local servers (including localhost) in your company's domain could be blacklisted with the exception of say one server designated to share images. – HTKLee Jun 18 '16 at 09:22
  • Also since your application is fetching images from other servers, a rate limiter to ensure the app doesn't flood the target URLs with requests could be important. You don't want your users, whether by accident or otherwise to launch a DoS attack against the URL they specify using your app. – HTKLee Jun 18 '16 at 09:31
  • @HTLee - Whitelist - no, that would be too restrictive. Blacklist - while not ideal, that's what the current answer is based on. And yes, rate limiting is a good idea, but we need to solve the SSRF issue as well. – paj28 Jun 18 '16 at 12:49

1 Answers1

12

This is the code I'm currently using:

if(!url.getProtocol().startsWith("http"))
    throw new Exception();
InetAddress inetAddress = InetAddress.getByName(url.getHost());
if(inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress())
    throw new Exception();
HttpURLConnection conn = (HttpURLConnection)(url.openConnection());
conn.setInstanceFollowRedirects(false);
conn.connect();
IOUtils.copy(conn.getInputStream(), out);

It enforces HTTP or HTTPS, forbids local addresses, and disables redirects. However, it still has the TOCTOU (time of check, time of use) flaw.

Edit (2022): The best solution these days is to use a library specifically designed to safely fetch untrusted URLs. For example, Advocate for Python. I don't have a link to a similar Java project to hand; if someone can add this, I would appreciate it!

paj28
  • 32,906
  • 8
  • 93
  • 130
  • According to the jdk sources of [openjdk8](https://github.com/openjdk/jdk/blob/17f04fc9e755a83727ac9d13a39b487f99311efd/src/java.base/share/classes/java/net/InetAddress.java#L387-L411) the is*Address functions always return false. – thi gg Nov 09 '20 at 16:50