Accessing the Unseen: Scoped SSRF Exploitation Through Firewall and Reverse Proxy Misconfigurations
Last updated
Last updated
During one of my projects, I discovered a critical yet often under-appreciated SSRF vulnerability in the company’s infrastructure. This issue was caused by a misconfigured FortiGate WebFilter and an Nginx reverse proxy that forwarded all requests to upstream load balancers. By exploiting this flaw, I gained unauthorized access to the internal network, including sensitive services like GitLab, JFrog Artifactory, and Prometheus, as well as admin panels meant to be accessible only from whitelisted IP addresses. This vulnerability exposes significant gaps in the network’s perimeter security, highlighting the need for immediate remediation to protect internal resources.
Captivated by the new Timing Attack techniques introduced by James Kettle, I was probing a target for scoped SSRF where I observed suspicious behavior that seemed promising. Throughout this report, the first domain is referred to as example.com
, the second domain is bluh.example-2.com
, and the internal domain is internal-example.com
.
After becoming suspicious of my.example.com
, I attempted to change the Host header to one of the other public subdomains of example.com
, such as account.example.com
. The contents of account.example.com
were returned in the response, as demonstrated by the images below.
I then tried changing the Host header to a nonexistent domain, such as bluhbluh.example.com, and received a 404 response. Unsure whether this was due to a VHost configuration or a reverse proxy, I tested a Burp Collaborator payload. The response was a 403 error page, suggesting the presence of a firewall, which I later identified as FortiGate.
Subsequently, I searched for subdomains of example.com
on the internet and discovered admin.example.com
. When I pinged this address, I found it resolved to a public IP address, but I was unable to browse the domain in my browser, indicating that a firewall was restricting access to the admin panel. To further investigate, I changed the Host header of my.example.com
to admin.example.com
, and to my surprise, I received a 200 OK response.
At this point, I hypothesized that a FortiGate WebFilter was likely in place, blocking host headers other than subdomains of example.com
. To test this theory, I searched for other domains and subdomains belonging to the company but resolving to different IP addresses, hoping to find a misconfigured WebFilter that might allow access to other hosts.
After searching for a while, I found a domain that seemed to be less restricted or possibly unrestricted: bluh.example-2.com
. Using this domain, I sought clues about internal domain names and discovered one: internal-example.com
. I then fuzzed the subdomains of internal-example.com
and uncovered several valuable targets, including GitLab repositories, JFrog Artifactory, and monitoring applications such as Grafana and Prometheus.
Further analysis showed that these internal domains either lacked a FortiGate WebFilter or had a misconfigured one that allowed attackers to proxy through these IPs to other sites. Additionally, it was discovered that the company was using the same load balancer for both internal and public services. The upper-layer Nginx configuration was not using a catch-all block, which led to requests matching the first Nginx configuration that had a server_name
starting with the same letter as the spoofed Host header. This resulted in all requests being forwarded to load balancers, allowing access to both internal and public services.
Configure FortiGate WebFilter:
Strictly configure URL filters on your FortiGate WebFilter to explicitly allow only the domains and subdomains that should be accessible to users. Ensure that any unauthorized domains are blocked to prevent exploitation.
Update Nginx Configuration:
Set a catch-all block or default configuration for your Nginx proxies. This ensures that requests are handled consistently and prevents unintended behavior or errors resulting from misconfigured server_name directives.