> ## Documentation Index
> Fetch the complete documentation index at: https://guide.codepure.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Server-Side Request Forgery (SSRF)

> Architectural examples and mitigation for SSRF in Django, Spring Boot, Rails, Express, ASP.NET Core, and Laravel.

## Overview

Server-Side Request Forgery (SSRF) is a vulnerability where an attacker can coerce a server-side application to make HTTP requests to an arbitrary location. Instead of attacking the user, the attacker uses the application's server as a proxy to send crafted requests. This is especially dangerous in cloud environments, as it can be used to access internal-only services or cloud provider metadata endpoints.

## Business Impact

A successful SSRF attack can lead to the complete compromise of the server's cloud account by stealing credentials from metadata services. It allows an attacker to map out and interact with the internal network, bypass firewalls, and access sensitive internal services like databases, admin panels, or internal APIs that are not exposed to the internet.

<Card title="Reference Details" icon="book-open" iconType="solid">
  **CWE ID:** [CWE-918](https://cwe.mitre.org/data/definitions/918.html)
  **OWASP Top 10 (2021):** A10:2021 - Server-Side Request Forgery
  **Severity:** Critical
</Card>

## Framework-Specific Analysis and Remediation

No web framework provides built-in protection against SSRF because making outbound HTTP requests is a fundamental feature. The responsibility lies entirely with the developer to validate and sanitize any user-supplied data that is used to construct the URL for an outbound request. The most robust defense is a strict allow-list of permitted hosts.

<Tabs>
  <Tab title="Python">
    #### Framework Context

    Django applications commonly use the `requests` library to make outbound HTTP calls. The vulnerability arises when a URL, or part of a URL, is constructed from user input without proper validation.

    #### Vulnerable Scenario 1: Image Importer

    A feature allows users to import a profile picture by providing a URL. The server then fetches the image.

    ```python theme={null}
    # profiles/views.py
    import requests
    from django.http import HttpResponse

    def import_avatar(request):
        image_url = request.GET.get('url')
        # DANGEROUS: The server makes a request to any URL the user provides.
        # An attacker can provide "[http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-instance-role](http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-instance-role)"
        # to steal AWS credentials.
        try:
            response = requests.get(image_url, timeout=5)
            # ... process image ...
            return HttpResponse("Image imported.")
        except requests.RequestException:
            return HttpResponse("Could not fetch image.", status=400)
    ```

    #### Vulnerable Scenario 2: Webhook Notification

    A service allows users to configure a webhook URL to be notified of events.

    ```python theme={null}
    # webhooks/services.py
    def send_webhook_notification(webhook_url, data):
        # DANGEROUS: The server blindly sends a POST request to any configured URL.
        # This can be used to scan internal ports or attack internal services.
        # For example, "http://localhost:8080/admin/delete-all-users"
        requests.post(webhook_url, json=data)
    ```

    #### Mitigation and Best Practices

    Parse the user-provided URL, extract the hostname, and validate it against a strict allow-list of trusted domains. Never make a request to an IP address directly.

    #### Secure Code Example

    ```python theme={null}
    # profiles/views.py (Secure Version)
    import requests
    from urllib.parse import urlparse

    ALLOWED_IMAGE_HOSTS = {
        'images.unsplash.com',
        'pbs.twimg.com',
        'cloudinary.com'
    }

    def import_avatar(request):
        image_url = request.GET.get('url')
        try:
            parsed_url = urlparse(image_url)
            # 1. Check scheme
            if parsed_url.scheme not in ['http', 'https']:
                return HttpResponse("Invalid URL scheme.", status=400)
            
            # 2. Check hostname against allow-list
            if parsed_url.hostname not in ALLOWED_IMAGE_HOSTS:
                return HttpResponse("Host not allowed.", status=400)

            # SAFE: The request is only made after validation.
            response = requests.get(image_url, timeout=5)
            # ... process image ...
            return HttpResponse("Image imported.")
        except (ValueError, requests.RequestException):
            return HttpResponse("Invalid URL or could not fetch image.", status=400)
    ```

    #### Testing Strategy

    Write tests that attempt to request URLs pointing to `localhost`, internal IP ranges (e.g., `127.0.0.1`, `10.0.0.1`, `192.168.1.1`), and the cloud metadata service IP (`169.254.169.254`). Use a mocking library like `responses` or `unittest.mock` to intercept the outbound HTTP request and assert that a request is *not* made for disallowed hosts.

    ```python theme={null}
    # profiles/tests.py
    import responses

    @responses.activate
    def test_import_avatar_ssrf_attempt_is_blocked(self):
        # The AWS metadata service IP
        ssrf_payload_url = "[http://169.254.169.254/metadata](http://169.254.169.254/metadata)"
        
        # We don't need to mock the request itself because a properly
        # secured function should never even try to make the call.
        
        response = self.client.get(reverse('import-avatar'), {'url': ssrf_payload_url})
        
        self.assertEqual(response.status_code, 400)
        self.assertIn("Host not allowed", response.content.decode())
        
        # Verify that no outbound HTTP call was actually made.
        self.assertEqual(len(responses.calls), 0)
    ```
  </Tab>

  <Tab title="Java">
    #### Framework Context

    Java applications typically use `HttpClient`, `RestTemplate`, or `WebClient` for outbound requests. The vulnerability is language-agnostic and depends on the developer's failure to validate URLs before passing them to these clients.

    #### Vulnerable Scenario 1: PDF Generation from Webpage

    A service that converts a webpage to a PDF by fetching its HTML content.

    ```java theme={null}
    // service/PdfGeneratorService.java
    @Service
    public class PdfGeneratorService {
        @Autowired
        private RestTemplate restTemplate;

        public byte[] generateFromUrl(String urlString) {
            // DANGEROUS: The server will make a GET request to any URL passed in,
            // including internal ones like "[http://internal-dashboard.service.local](http://internal-dashboard.service.local)"
            String htmlContent = restTemplate.getForObject(urlString, String.class);
            // ... logic to convert HTML to PDF ...
        }
    }
    ```

    #### Vulnerable Scenario 2: API Data Proxy

    An endpoint that acts as a proxy to fetch data from another API, specified by the user.

    ```java theme={null}
    // controller/ProxyController.java
    @GetMapping("/proxy")
    public String proxyRequest(@RequestParam String targetApi) {
        // DANGEROUS: An attacker can point this to internal APIs.
        return new RestTemplate().getForObject(targetApi, String.class);
    }
    ```

    #### Mitigation and Best Practices

    Use Java's `java.net.URI` class to robustly parse the URL. Extract the host and validate it against a predefined `Set` of allowed domains. Additionally, configure the HTTP client to prevent it from following redirects, which can be used to bypass validation.

    #### Secure Code Example

    ```java theme={null}
    // service/PdfGeneratorService.java (Secure Version)
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.util.Set;

    @Service
    public class PdfGeneratorService {
        private final RestTemplate restTemplate;
        private final Set<String> allowedHosts = Set.of("en.wikipedia.org", "[www.bbc.com](https://www.bbc.com)");

        public PdfGeneratorService(RestTemplateBuilder restTemplateBuilder) {
            // Configure RestTemplate to not follow redirects
            this.restTemplate = restTemplateBuilder
                .requestFactory(() -> new SimpleClientHttpRequestFactory() {
                    @Override
                    protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
                        super.prepareConnection(connection, httpMethod);
                        connection.setInstanceFollowRedirects(false);
                    }
                }).build();
        }

        public byte[] generateFromUrl(String urlString) throws URISyntaxException {
            URI uri = new URI(urlString);
            String host = uri.getHost();

            if (host == null || !allowedHosts.contains(host.toLowerCase())) {
                throw new IllegalArgumentException("Host is not allowed: " + host);
            }
            
            // SAFE: Request is made only after host validation.
            String htmlContent = restTemplate.getForObject(uri.toString(), String.class);
            // ... logic to convert HTML to PDF ...
        }
    }
    ```

    #### Testing Strategy

    Write JUnit tests that call the service method with various malicious URLs (e.g., `file:///etc/passwd`, `http://127.0.0.1:8080`, `http://169.254.169.254`). The tests should assert that an `IllegalArgumentException` (or a similar custom exception) is thrown and that no outbound request is attempted.

    ```java theme={null}
    // src/test/java/com/example/PdfGeneratorServiceTest.java
    @Test
    void generateFromUrl_withInternalIp_shouldThrowException() {
        String internalUrl = "[http://127.0.0.1/admin](http://127.0.0.1/admin)";
        
        assertThrows(IllegalArgumentException.class, () -> {
            pdfGeneratorService.generateFromUrl(internalUrl);
        });
    }

    @Test
    void generateFromUrl_withMetadataService_shouldThrowException() {
        String metadataUrl = "[http://169.254.169.254/](http://169.254.169.254/)";
        
        var exception = assertThrows(IllegalArgumentException.class, () -> {
            pdfGeneratorService.generateFromUrl(metadataUrl);
        });
        
        assertTrue(exception.getMessage().contains("Host is not allowed"));
    }
    ```
  </Tab>

  <Tab title=".NET(C#)">
    #### Framework Context

    SSRF vulnerabilities in ASP.NET Core applications are common when using `HttpClient` or the legacy `WebClient` with user-controlled URLs. The remediation pattern is consistent: robust URL parsing and hostname validation.

    #### Vulnerable Scenario 1: A "Site Screenshot" Service

    A service that takes a screenshot of a webpage by fetching it.

    ```csharp theme={null}
    // Services/ScreenshotService.cs
    public class ScreenshotService
    {
        private readonly HttpClient _httpClient;
        public ScreenshotService(HttpClient httpClient) { _httpClient = httpClient; }

        public async Task<byte[]> TakeScreenshot(string url)
        {
            // DANGEROUS: The HttpClient will request any URL, including internal ones.
            var response = await _httpClient.GetStringAsync(url);
            // ... logic to render HTML and take screenshot ...
        }
    }
    ```

    #### Vulnerable Scenario 2: Checking for `robots.txt`

    A tool for SEO analysis that fetches the `robots.txt` file from a user-submitted domain.

    ```csharp theme={null}
    // Controllers/SeoController.cs
    [HttpGet("check-robots")]
    public async Task<IActionResult> CheckRobots([FromQuery] string domain)
    {
        var url = $"http://{domain}/robots.txt";
        // DANGEROUS: Attacker can provide "localhost:5000" or an internal service name.
        var robotsContent = await _httpClient.GetStringAsync(url);
        return Ok(robotsContent);
    }
    ```

    #### Mitigation and Best Practices

    Use the `System.Uri` class to parse and validate URLs. Compare the `uri.Host` against a configured allow-list of domains. Ensure the HTTP client is configured to disallow redirects, which could otherwise be used to circumvent hostname checks.

    #### Secure Code Example

    ```csharp theme={null}
    // Services/ScreenshotService.cs (Secure Version)
    public class ScreenshotService
    {
        private readonly HttpClient _httpClient;
        private readonly IConfiguration _config;

        public ScreenshotService(IHttpClientFactory httpClientFactory, IConfiguration config)
        {
            // Best practice: configure HttpClient to not follow redirects for SSRF protection
            var handler = new HttpClientHandler { AllowAutoRedirect = false };
            _httpClient = httpClientFactory.CreateClient();
            _httpClient.DefaultRequestHeaders.Clear(); // Example setup
            _config = config;
        }

        public async Task<byte[]> TakeScreenshot(string url)
        {
            if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
            {
                throw new ArgumentException("Invalid URL format");
            }

            var allowedHosts = _config.GetSection("AllowedScreenshotHosts").Get<string[]>();
            
            if (uri.HostNameType != UriHostNameType.Dns || !allowedHosts.Contains(uri.Host))
            {
                throw new ArgumentException("Host is not allowed.");
            }
            
            // SAFE: Request is only made after validation.
            var response = await _httpClient.GetStringAsync(uri);
            // ... logic to render HTML and take screenshot ...
        }
    }
    ```

    #### Testing Strategy

    Use xUnit and a mocking library like `Moq` or a test handler like `MockHttpMessageHandler` to test the service. The tests should confirm that an `ArgumentException` is thrown for disallowed hosts and that the `HttpClient`'s `SendAsync` method is never called in those cases.

    ```csharp theme={null}
    // Tests/ScreenshotServiceTests.cs
    [Fact]
    public async Task TakeScreenshot_WithDisallowedHost_ShouldThrowArgumentException()
    {
        // Arrange
        var mockHttpFactory = new Mock<IHttpClientFactory>();
        var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string> {
            {"AllowedScreenshotHosts:0", "example.com"}
        }).Build();
        var service = new ScreenshotService(mockHttpFactory.Object, config);
        var ssrfUrl = "[http://internal-service.local/admin](http://internal-service.local/admin)";
        
        // Act & Assert
        await Assert.ThrowsAsync<ArgumentException>(() => service.TakeScreenshot(ssrfUrl));
    }
    ```
  </Tab>

  <Tab title="PHP">
    #### Framework Context

    Laravel's `Http` client (a wrapper around Guzzle) makes outbound requests easy. The vulnerability lies in the business logic that constructs the URL, not the client itself.

    #### Vulnerable Scenario 1: Social Media Profile Importer

    A feature to import user data by providing a link to their public profile on another platform.

    ```php theme={null}
    // app/Http/Controllers/ImportController.php
    use Illuminate\Support\Facades\Http;

    class ImportController extends Controller
    {
        public function import(Request $request)
        {
            $profileUrl = $request->input('url');
            // DANGEROUS: The server makes a request to any URL the user provides.
            $response = Http::get($profileUrl);
            // ... logic to parse and import data ...
        }
    }
    ```

    #### Vulnerable Scenario 2: RSS Feed Parser

    A blog aggregator fetches and parses an RSS feed from a URL submitted by a user.

    ```php theme={null}
    // app/Jobs/FetchRssFeed.php
    class FetchRssFeed implements ShouldQueue
    {
        protected $feedUrl;
        public function __construct(string $feedUrl) { $this->feedUrl = $feedUrl; }

        public function handle(): void
        {
            // DANGEROUS: The job will make a request to any URL passed to it.
            $response = Http::get($this->feedUrl);
            // ... parse RSS ...
        }
    }
    ```

    #### Mitigation and Best Practices

    Use PHP's built-in `parse_url()` function to extract the host from the user-provided URL. Validate this host against an allow-list stored in your application's configuration. Configure the HTTP client to disable redirects.

    #### Secure Code Example

    ```php theme={null}
    // app/Http/Controllers/ImportController.php (Secure Version)
    use Illuminate\Support\Facades\Http;
    use Illuminate\Support\Facades\Log;

    class ImportController extends Controller
    {
        public function import(Request $request)
        {
            $profileUrl = $request->input('url');
            $host = parse_url($profileUrl, PHP_URL_HOST);
            
            // Allow-list is stored in config/services.php
            $allowedHosts = config('services.allowed_import_hosts');

            if (!$host || !in_array($host, $allowedHosts)) {
                Log::warning('SSRF attempt blocked for host: ' . $host);
                return back()->withErrors(['url' => 'The provided URL is not from an allowed source.']);
            }

            // SAFE: Request is made without redirects and only to allowed hosts.
            $response = Http::withoutRedirecting()->get($profileUrl);
            // ... logic to parse and import data ...
        }
    }
    ```

    #### Testing Strategy

    Write a PHPUnit feature test that posts a disallowed URL to the import endpoint. Assert that the application returns a validation error and that no outbound request was actually made by using Laravel's `Http::fake()` facade.

    ```php theme={null}
    // tests/Feature/ImportControllerTest.php
    public function test_import_blocks_ssrf_attempts()
    {
        Http::fake(); // Prevent any real HTTP requests from being made
        
        $user = User::factory()->create();
        $disallowedUrl = '[http://127.0.0.1/server-status](http://127.0.0.1/server-status)';
        
        $response = $this->actingAs($user)->post('/import-profile', ['url' => $disallowedUrl]);
        
        $response->assertSessionHasErrors('url');
        
        // Assert that no request was sent to the disallowed URL
        Http::assertNothingSent();
    }
    ```
  </Tab>

  <Tab title="Node.js">
    #### Framework Context

    In the Node.js ecosystem, libraries like `axios` or the built-in `https` module are used for requests. The principles remain the same: validate URLs before use. The built-in `URL` class is the standard tool for parsing.

    #### Vulnerable Scenario 1: A URL Preview Generator

    An API endpoint that fetches a URL's metadata (title, description, image) to generate a preview card.

    ```javascript theme={null}
    // routes/preview.js
    const express = require('express');
    const router = express.Router();
    const axios = require('axios');

    router.get('/', async (req, res) => {
        const { url } = req.query;
        try {
            // DANGEROUS: Makes a GET request to any user-provided URL.
            const response = await axios.get(url);
            // ... logic to parse HTML and extract metadata ...
            res.json({ title: "Some Title" });
        } catch (error) {
            res.status(500).send('Error fetching URL');
        }
    });
    module.exports = router;
    ```

    #### Vulnerable Scenario 2: Dynamic Badge Service

    A service that generates a status badge by querying a user-provided API endpoint.

    ```javascript theme={null}
    // services/badgeService.js
    async function getBadgeStatus(apiUrl) {
        // DANGEROUS: Can be pointed to internal monitoring endpoints.
        const { data } = await axios.get(apiUrl);
        return data.status;
    }
    ```

    #### Mitigation and Best Practices

    Use the WHATWG `URL` object for robust parsing. Check the `protocol` and `hostname` properties against a strict allow-list. Configure your HTTP client to disable redirects.

    #### Secure Code Example

    ```javascript theme={null}
    // routes/preview.js (Secure Version)
    const { URL } = require('url');

    const ALLOWED_HOSTS = new Set(['github.com', 'developer.mozilla.org']);

    router.get('/', async (req, res) => {
        const { url } = req.query;
        let parsedUrl;
        try {
            parsedUrl = new URL(url);
        } catch (err) {
            return res.status(400).send('Invalid URL format');
        }
        
        if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
            return res.status(400).send('Invalid protocol');
        }

        if (!ALLOWED_HOSTS.has(parsedUrl.hostname)) {
            return res.status(400).send('Host not allowed');
        }

        try {
            // SAFE: Request is made only after validation, and redirects are disabled.
            const response = await axios.get(parsedUrl.toString(), { maxRedirects: 0 });
            // ... logic to parse HTML ...
            res.json({ title: "Some Title" });
        } catch (error) {
            res.status(500).send('Error fetching URL');
        }
    });
    ```

    #### Testing Strategy

    Use Jest and a mocking library like `nock` or Jest's built-in mocking to test the endpoint. The test should attempt to request a disallowed URL and assert that the HTTP client (`axios.get`) was never called with that URL.

    ```javascript theme={null}
    // tests/preview.test.js
    const axios = require('axios');
    jest.mock('axios'); // Mock the entire axios library

    it('should not make a request to a disallowed host', async () => {
        const app = require('../app'); // Your express app
        const request = require('supertest');
        
        const response = await request(app).get('/api/preview?url=[http://127.0.0.1](http://127.0.0.1)');
        
        expect(response.statusCode).toBe(400);
        expect(response.text).toContain('Host not allowed');
        
        // Verify that the mocked axios.get was never called.
        expect(axios.get).not.toHaveBeenCalled();
    });
    ```
  </Tab>

  <Tab title="Ruby">
    #### Framework Context

    Outbound requests in Rails are typically made with the built-in `Net::HTTP` library or higher-level gems like `HTTParty` or `Faraday`. As with other frameworks, the vulnerability lies in the logic that constructs and validates the URL before the request is made.

    #### Vulnerable Scenario 1: A URL Expander Service

    A simple service that takes a shortened URL and returns the final destination URL after following redirects.

    ```ruby theme={null}
    # app/controllers/url_expander_controller.rb
    require 'net/http'

    class UrlExpanderController < ApplicationController
      def expand
        short_url = params[:url]
        # DANGEROUS: The application will connect to any host and follow redirects
        # to any other host, allowing an attacker to probe the internal network.
        response = Net::HTTP.get_response(URI(short_url))
        final_url = response['location'] || short_url
        render json: { final_url: final_url }
      end
    end
    ```

    #### Vulnerable Scenario 2: Webmention Receiver

    A blog feature that receives a "webmention" (a notification that another site has linked to it) and fetches the source URL to verify the link.

    ```ruby theme={null}
    # app/services/webmention_verifier.rb
    class WebmentionVerifier
      def self.verify(source_url, target_url)
        # DANGEROUS: The `source_url` is provided by an external, untrusted actor.
        response = HTTParty.get(source_url)
        # ... logic to check if the response body contains a link to target_url ...
      end
    end
    ```

    #### Mitigation and Best Practices

    Use Ruby's `URI` module to parse URLs. Check the parsed host against an allow-list. When making requests, explicitly disable following redirects if they are not necessary, or validate each redirect location if they are.

    #### Secure Code Example

    ```ruby theme={null}
    # app/services/webmention_verifier.rb (Secure Version)
    class WebmentionVerifier
      ALLOWED_DOMAINS = ['some-blog.com', 'another-trusted-site.org'].freeze

      def self.verify(source_url, target_url)
        begin
          uri = URI.parse(source_url)
        rescue URI::InvalidURIError
          return { valid: false, reason: "Invalid URL" }
        end
        
        unless ALLOWED_DOMAINS.include?(uri.host)
          return { valid: false, reason: "Host not allowed" }
        end
        
        # SAFE: Make the request only after validation.
        # HTTParty can be configured not to follow redirects as well.
        response = HTTParty.get(uri.to_s, no_follow: true)
        # ... logic to check response body ...
      end
    end
    ```

    #### Testing Strategy

    Use RSpec to test the service object directly. Use a mocking library like `WebMock` to control the HTTP requests. The test should attempt to verify a URL from a disallowed host and assert that `WebMock` never received a request to that host.

    ```ruby theme={null}
    # spec/services/webmention_verifier_spec.rb
    require 'rails_helper'

    RSpec.describe WebmentionVerifier do
      it "does not make an HTTP request to a disallowed host" do
        disallowed_url = "[http://internal.service/private-data](http://internal.service/private-data)"
        # WebMock will raise an error if an unexpected HTTP request is made.
        
        result = WebmentionVerifier.verify(disallowed_url, "[http://my-site.com/post](http://my-site.com/post)")
        
        expect(result[:valid]).to be(false)
        expect(result[:reason]).to eq("Host not allowed")
      end
    end
    ```
  </Tab>
</Tabs>
