Due to the positive response I got on my previous write up, I figured I’d keep the ball rolling and do another. Thank you to everyone who shared the last post, and I hope that you find this write up just as enjoyable.

Tutorial Mode

Server Side Request Forgery (SSRF) is just a fun bug to find. For those of you who are new to application security or bug bounties, I’ll go over what SSRF is and how you can discover/exploit it. If you’re on the experienced side, or that just sounds terribly boring, you can skip down to the the write up.

A Server Side Request Forgery is a vulnerability that an attacker can use to send a request from the vulnerable web server. Google image search returned a few mediocre diagrams and a bunch of pictures from the Spiritual Sciences Research Foundation… so i made my own.

ssrf diagram.png

Fancy stuff, right? Burp Suite’s Collaborator is going to be your best friend when searching for SSRF. Collaborator provides a URL that you can inject into parameters that you suspect to be vulnerable, then lets you know if if it receives any requests.

So, what can we do with this nifty SSRF bug? It really depends on what’s happening on the back-end, but here are a few examples:

  • Port scan their internal network
  • Read internally hosted files/data
  • Access services listening on the loopback interface (127.0.0.1)
  • Read local system files (using file://)
  • If it’s hosted on AWS, access the AWS REST interface (http://169.254.169.254/)

See? Kinda fun. My write up uses a proof-of-concept where I discovered a bunch of internal IP addresses and then port scanned them.

The Write Up

Again, I just want to thank [redacted] for letting me share these bugs. You’ll notice that a bunch of information is redacted, as this was from an invite-only program and they kindly agreed to let me do a write up as long as it was removed.

Down to business. So I was doing a bit of exploring through a domain, just trying to get a feel for how everything worked together, when I came across this request:

GET /interrogate?Url=https%3A%2F%2Fwww.hackmenow.com%2Fresources%2Ftest.pdf HTTP/1.1
Host: [redacted]
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Content-Type: application/json
Referer: [redacted]
Connection: close

Now, if you’ve been hunting bugs for a while, and see a URL parameter that’s not a redirect, your SSRF alarms start going off. So I opened up Burp Collaborator, put the generated URL in the Url parameter, and (as you’d expect) started getting requests from the back-end server.

Request

GET /interrogate?Url=http%3A%2F%2Fiaw7p2gj1pqzrpkdgofjbjri0961uq.burpcollaborator.net HTTP/1.1
Host: [redacted]
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Content-Type: application/json
Referer: [redacted]
Connection: close

ssrf writeup burpcollab

Great. The SSRF exists, but what can I do with this? Here’s the problem. There may not be any input validation, but there’s certainly validation of the output format. If I try to request an internal site or local file, even if the site/file exists and content is retrieved, the service returns an error if it’s not in the JSON format it’s expecting. The error looks something like this:

{ “exception” : “‘.’, hexadecimal value 0x00, is an invalid character. Line 1, position 1.” }

For some reason, I thought of the Oracle SSRF from a few years ago (CVE 2014-4210). Basically, you could use a SSRF in some test functionally to port scan the internal network based on the different error messages you got.

giphy

Sounds like a good idea to me. But where to start? I can’t just blast the web server with every address in the private IP blocks…

ssrf hi

Oh… hey. Turns out the requests in Burp Collaborator had an internal IP address in the X-Forwarded-For header. That gave me a target to work around. So given the IP address xxx.xx.x5.252, it’s safe to say there are other accessible addresses between xxx.xx.x4.1 and xxx.xx.x6.255. Now I just need to determine what error messages indicate a valid host. After some experimenting, I could consistently identify active hosts using the following:


Active Host – HTTP Response

400 Bad Request

{ “exception” : “For security reasons DTD is prohibited in this XML document. To enable DTD processing set the DtdProcessing property on XmlReaderSettings to Parse and pass the settings into XmlReader.Create method.” }

Active Host – Null Response

400 Bad Request

{ “exception” : “‘.’, hexadecimal value 0x00, is an invalid character. Line 1, position 1.” }

Active Host – Forbidden Directory/Authentication Required

400 Bad Request

{ “exception” : “The remote server returned an error: (403) Forbidden.” }

Inactive Host

504 GATEWAY_TIMEOUT


 

After running Burp Intruder with a larger list of IP addresses, I was able to identify 19 IP addresses. Some returned HTML responses, but most returned the null response. The final step would be to identify which ports are open on each of the hosts, modifying the request like this:

GET /interrogate?Url=123.123.12.12:21 HTTP/1.1
Host: [redacted]
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Content-Type: application/json
Referer: [redacted]
Connection: close

I was concerned that the back-end wouldn’t properly make the request after appending the ports, so I used a public facing site with several varied open ports to see if the error message patterns remained the same… which they did not…

giphy1

Until I saw…….

ssrf timing 1

ssrf timing 2

The response times!!! There were some distinct groupings in the total response times, and they happened to match up with the ports I knew to be open. All of the requests highlighted in red were requests to ports I knew were open. The one request highlighted in yellow (port 443) was a false positive. Port 443 was not open.

After testing out a couple other public IP addresses, I was able to confidently say that a port was open if:

  1. The response time is around 295 (upper limit of roughly 680-700)
  2. The response time is around 2799 (lower limit of roughly 1700), or the request returns a timeout error.

After running the same tests on a few of the internal IP addresses, I compiled all of the discovered IPs and their open ports and sent in the report. The report was accepted and rewarded as a P3 (which was fine, since it matches with the Oracle CVE mentioned earlier).

Going from browsing a site to port scanning their internal network, that’s why I love SSRFs.

 

 

About Jean Fleury

Naval officer, privateer, cyber security professional. Traded in my five-ship squadron for a computer and Burp Suite license.

Category

Security Research, Web Application Penetration Testing