Sometimes, walls get in the way, and when that happens, we need a door. A door needs a proper lock, or a security vulnerability may result. Server-side request forgery (SSRF) vulnerabilities can manifest in a number of ways, but usually it’s because a door was installed without a lock.

The same-origin policy (SOP) is a wall every browser uses to keep users safe. If this wall didn’t exist, then while you are reading this blog post, JavaScript on this page would be allowed to interact with arbitrary domains. For example, malicious JavaScript could make a request to https://gmail.com, and access your email.

Clearly, this should not be permitted, and is the reason why the same-origin policy exists.

SSRF by Design

Like any wall, though, the SOP tends to get in the way of application development. Normally, the JavaScript object XMLHttpRequest(), used to execute asynchronous HTTP requests, is restricted by browsers through the SOP so that it can only fetch content from the same domain or “origin.” However, there are ways of bypassing this aspect of SOP. One way that web application developers sidestep this restriction is by using a cross-domain proxy.

In our analogy, a cross-domain proxy is a door into another domain — in more technical terms, it is a server-side component that makes HTTP requests on the client’s behalf, and returns the HTTP response so that the client can load content that is normally off limits due to the SOP.

Reusing what exists is usually a good engineering principle, and when I searched for “cross-domain proxy” on Google, the first search result was vulnerable to SSRF under the right circumstances. Out of the box, the proxy.php file is disabled, and it requires configuration before it can be used. One of the configuration options is CSAJAX_FILTER_DOMAIN. If this constant is set to true, then proxy.php can be used to access any domain.

Delving a bit deeper into the details, the PHP cross-domain proxy is typically used by JavaScript to fetch content from another domain, as shown by the following jQuery statement:

$('#target').load('/proxy/proxy.php?csurl=https://www.google.com/finance?q=gdx');

The above JavaScript will access the proxy.php file on the same domain, which will in turn load the Google Finance page for the GDX ticker symbol and return the results to the client. This is not an example of a security vulnerability because anyone can fetch pages on Google Finance. What transforms a cross-domain proxy into an exploitable and useful SSRF vulnerability is the ability to access otherwise inaccessible network segments. Returning to our analogy, setting the proxy configuration parameter CSAJAX_FILTER_DOMAIN to true is like creating a door without a lock. In some contexts, like with the Google Finance page, this is fine; in others, it is deadly.

Practical Exploitation of SSRF

Consider that a web application server is typically connected to network segments that are normally off limits to the larger Internet. A good example applicable to virtually all systems is local network interface address 127.0.0.1, commonly mapped to the hostname localhost. In a vulnerable configuration, an attacker can abuse proxy.php to access the CUPS HTTP daemon, which is typically only accessible at http://localhost:631. To do so, the attacker would make the following request:

http://target/proxy.php?csurl=http://localhost:631

Accessing the above URL should result in the following page:

As seen above, exploiting SSRF to access a daemon executing on localhost, such as CUPS, is a good method to confirm a successful remote connection to a private network.

The CUPS daemon is used to configure printers. Don’t get me wrong: Printers are fun, but they’re not really our goal. A shell would be nice, and older systems running the CUPS daemon on http://localhost:631 were vulnerable to Shellshock. The CUPS daemon runs as root, so the successful exploitation of Shellshock in this context results in root privileges. The CUPS Shellshock vulnerability can be remotely exploited by sending POST requests through an SSRF vulnerability, which is supported by proxy.php.

Further Down the Rabbit Hole

There are numerous attack opportunities that become available when SSRF is used to access internal networks. To help identify these opportunities, the Burp Intruder’s Cluster Bomb attack type can be used to make proxy.php into a useful port scanner for exploring the internal network. To execute this attack, start by configuring Intruder as shown in the following screenshot:

The above Cluster Bomb attack type will use every permutation of the two lists represented by the $localhost$ and $631$ placeholders. The former list contains IP addresses that we want to scan, and the latter is a list of ports that may be running an HTTP daemon. Executing this attack in a test environment resulted in the following:

Above, we see that an HTTP server running on 192.168.201.1 is accessible because it returned an HTTP 200 response. When accessed using SSRF, the following authentication page was exposed:

Conclusion

Do administrative accounts used only on the internal network require strong passwords? The short answer is; however, weak passwords are a disturbingly common finding on internal network assessments, or in this case, an external penetration test that broke into the internal network.

The solution to this problem is that application developers needs to be more careful about the holes made in walls; a same-origin policy bypass needs to be more restrictive than a wide-open cross-domain proxy. Cross-origin resource sharing (CORS), introduced in HTML5, was created specifically to allow for a selective bypass of same-origin policy, and should be used in place of a cross-domain proxy whenever possible. In the days before CORS, developers needed to construct their own creative bypasses to the SOP, such as a cross-domain proxy, or JSON with Padding (JSONP)*. Developers will find creative and interesting solutions to difficult problems, but these are not always secure solutions.

CORS isn’t a one-size-fits-all solution, though. If you need to use proxy.php, set CSAJAX_FILTERS to true and define the list of $valid_requests, which are whitelisted requests that the server is permitted to perform on the client’s behalf. Setting CSAJAX_FILTER_DOMAINS to false will ensure that proxy.php will only send the exact requests defined within $valid_requests. Meanwhile, setting the CSAJAX_FILTER_DOMAINS to true will only filter by domain name, which could still lead to a compromise.

Another approach to consider is the use of a cross-domain proxy service — and let someone else take this dangerous functionally off your hands.

Security can be a grueling process, and when the days drag on, sometimes developers notice holes in the wall and look the other way — without taking into consideration the ramifications.

(Note: It is important to note that JSONP can also lead to serious vulnerabilities and must be used with caution.)