Back to Blog
Lesson 30 of the Advanced Laravel: Architecture, Scaling & Performance course
LaravelJune 28, 20263 min read

Defense Against SSRF: Hardening Laravel Outgoing Requests

SSRF allows attackers to force your server to make unauthorized requests. Learn to secure your Laravel application by validating URLs and restricting targets.

LaravelSecuritySSRFHardeningInfrastructurephpbackend

Previously in this course, we discussed multi-tenant security, focusing on isolating data at the database level. Today, we shift our focus from incoming data to outgoing traffic. Server-Side Request Forgery (SSRF) occurs when an attacker influences an application to fetch resources from internal or unauthorized external locations, essentially turning your server into a proxy for malicious activity.

In our SaaS project, we frequently fetch metadata or webhooks for users. Left unchecked, a user could provide an internal IP address (like 169.254.169.254) to exfiltrate cloud credentials or scan your internal microservices.

Understanding the SSRF Attack Vector

At its core, SSRF exploits the trust your internal network places in your web servers. Because your server resides within a VPC or a private network, it often has access to services that are not exposed to the public internet.

When you use Http::get($userProvidedUrl), Laravel performs a DNS resolution and initiates a connection. If an attacker provides a URL targeting your internal Redis instance or a management interface, your server will dutifully execute that request, potentially leaking sensitive information or triggering unintended state changes.

Hardening Outgoing Requests

To defend against SSRF, we must treat all user-provided URLs as inherently hostile. We do this through three layers of defense:

  1. Strict URL Validation: Use regex and PHP's filter_var to ensure the URL follows expected patterns.
  2. Allow-listing: Limit requests to known, trusted domains.
  3. Network Isolation: Block access to loopback addresses (127.0.0.1) and private IP ranges (e.g., 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16).

Worked Example: The Secure Fetcher Service

Instead of calling the Http facade directly in our controllers, we will encapsulate this logic in a dedicated service. This ensures that every outgoing request passes through our security filters.

PHP
namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use InvalidArgumentException;

class SecureHttpClient
{
    private const ALLOWED_HOSTS = ['api.trusted-partner.com', 'webhooks.customer.com'];

    public function get(string $url)
    {
        $parsed = parse_url($url);

        #6A9955">// 1. Validate the host is in our allowed list
        if (!in_array($parsed['host'] ?? '', self::ALLOWED_HOSTS)) {
            throw new InvalidArgumentException("Unauthorized destination host.");
        }

        #6A9955">// 2. Resolve the IP to prevent DNS Rebinding attacks
        $ip = gethostbyname($parsed['host']);
        if ($this->isInternalIp($ip)) {
            throw new InvalidArgumentException("Internal network access prohibited.");
        }

        return Http::get($url);
    }

    private function isInternalIp(string $ip): bool
    {
        return !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
    }
}

Hands-on Exercise

  1. Create a new App\Services\SecureHttpClient class in your project.
  2. Implement a validateUrl method that checks for http or https schemes only.
  3. Integrate this service into your existing webhook-processing logic (advancing our SaaS project's integration module).
  4. Write a test case that attempts to hit http://127.0.0.1 and asserts that it throws an InvalidArgumentException.

Common Pitfalls

  • Trusting DNS: Attackers can use DNS rebinding to bypass initial checks. Always resolve the domain to an IP address before making the request and validate that specific IP.
  • Following Redirects: By default, many HTTP clients (including Guzzle, which Laravel uses) follow redirects. If you validate the first URL but the response redirects to an internal IP, you remain vulnerable. Ensure you disable automatic redirects or validate the final destination URL.
  • Ignoring IPv6: FILTER_VALIDATE_IP can be tricky with IPv6 addresses. Ensure your validation logic covers both protocol versions.

Summary

Securing your infrastructure against SSRF is not just about blocking IP addresses; it's about adopting a "zero-trust" approach to your own egress traffic. By validating the target host and checking the resolved IP address before initiating the request, you effectively neutralize the most common vectors for this vulnerability.

As we continue to harden our architecture, remember that preventing blind SSRF is equally important if your application interacts with cloud metadata services.

Up next: We will discuss Mass Assignment Hardening, ensuring that your models are protected from malicious attribute injection.

Similar Posts