CVE-2021-47776

Umbraco Unchained: The 'baseUrl' SSRF Nightmare

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 16, 2026·5 min read

Executive Summary (TL;DR)

Umbraco v8.14.1 trusted user input a little too much. Specifically, the BackOffice API blindly accepted a `baseUrl` parameter to fetch help documentation and dashboard widgets. An authenticated attacker could modify this parameter to point to `http://169.254.169.254` (or any internal IP), forcing the server to fetch and display sensitive data like AWS credentials or internal service responses. The fix involves implementing a strict allowlist for remote resource fetching.

A classic Server-Side Request Forgery (SSRF) vulnerability in the Umbraco CMS BackOffice allows authenticated users to turn the web server into an open proxy. By manipulating the `baseUrl` parameter in help and dashboard controllers, attackers can scan internal networks or exfiltrate cloud metadata.

The Hook: Who Needs a Proxy When You Have a CMS?

Umbraco is the heavy hitter of the .NET CMS world. It's flexible, powerful, and usually, pretty secure. But in version 8.14.1, a feature designed to be helpful—fetching context-sensitive documentation and dashboard widgets—decided to be a little too helpful.

Here's the setup: The Umbraco BackOffice (the admin panel) needs to show you help articles or remote dashboard content. Ideally, the server should know where this content lives. Maybe a config file? A hardcoded constant?

Nope. The developers decided to let the client tell the server where to fetch the data from. It’s the architectural equivalent of a bank teller asking the robber, "Which vault would you like me to open for you today?" If you have access to the BackOffice, you have the ability to make the server HTTP-request whatever you want.

The Flaw: Trusting the `baseUrl`

The vulnerability lies in how the HelpController and DashboardController handle external requests. Specifically, three endpoints were identified as vulnerable:

  1. /umbraco/BackOffice/Api/Help/GetContextHelpForPage
  2. /umbraco/backoffice/UmbracoApi/Dashboard/GetRemoteDashboardContent
  3. /umbraco/backoffice/UmbracoApi/Dashboard/GetRemoteDashboardCss

All three accept a parameter conveniently named baseUrl. The logic is painfully simple: take the baseUrl, append a specific path (like a help topic ID), and execute an HTTP GET request from the server context.

There was no validation. No whitelist. No regex checking if the URL started with https://our.umbraco.com. If you passed http://google.com, the server fetched Google. If you passed http://localhost:3389, the server knocked on its own RDP port. It’s a textbook Server-Side Request Forgery (SSRF), packaged neatly in a standard API controller.

The Code: The Smoking Gun

Let's look at the logic inside Umbraco.Web.Editors.HelpController.cs. While the exact original source is often scrubbed, the logic flow reconstruction reveals the sin.

The Vulnerable Logic (Conceptual):

[HttpGet]
public HttpResponseMessage GetContextHelpForPage(string section, string tree, string baseUrl)
{
    // 1. Trust the user input blindly
    if (string.IsNullOrEmpty(baseUrl))
        baseUrl = "https://our.umbraco.com"; // Default if empty, but overridable
 
    // 2. Construct the full URL
    // The server appends the path, but the host is fully controlled by the attacker
    var fullUrl = string.Format("{0}/rss/help?section={1}&tree={2}", baseUrl, section, tree);
 
    // 3. Fire the request
    using (var client = new HttpClient())
    {
        // This request comes FROM the web server's IP address
        var response = client.GetAsync(fullUrl).Result;
        return response;
    }
}

The Fix:

The remediation wasn't rocket science. It was simply a matter of stripping the baseUrl parameter of its power or strictly validating it against a list of known, safe domains.

// The Remediation Approach
private static readonly string[] AllowedDomains = { "our.umbraco.com", "umbraco.tv" };
 
public HttpResponseMessage GetContextHelpForPage(string section, string tree, string baseUrl)
{
    // Check if the requested baseUrl is in the allowed list
    var uri = new Uri(baseUrl);
    if (!AllowedDomains.Contains(uri.Host))
    {
        return new HttpResponseMessage(HttpStatusCode.Forbidden);
    }
    // ... proceed ...
}

The Exploit: Knocking on Internal Doors

So, we have an authenticated SSRF. How do we weaponize this? The most damaging attack vector in modern hosting environments is cloud metadata exfiltration.

If this Umbraco instance is hosted on AWS, Azure, or GCP, it likely has access to the metadata service at 169.254.169.254. This service requires no authentication other than the request coming from the instance itself—which is exactly what SSRF provides.

The Attack Chain:

  1. Login: Authenticate to the Umbraco BackOffice. (Low privilege is often enough, as long as you can access the dashboard).
  2. Craft the Request: Send a GET request to the GetRemoteDashboardContent endpoint.
  3. Target Metadata: Set the baseUrl to the EC2 metadata endpoint.

PoC Payload:

GET /umbraco/backoffice/UmbracoApi/Dashboard/GetRemoteDashboardContent?section=News&baseUrl=http://169.254.169.254/latest/meta-data/iam/security-credentials/ HTTP/1.1
Host: target-umbraco-site.com
Cookie: [AuthCookies]

The Result: Instead of a nice dashboard widget containing Umbraco news, the server responds with a JSON blob containing the AWS Access Key ID and Secret Access Key for the server's IAM role. Game over.

[!NOTE] For the GetContextHelpForPage endpoint, the baseUrl didn't require a trailing slash, whereas the dashboard endpoints were pickier. Fuzzing small variations is key in SSRF research.

The Impact: Why Should We Panic?

This isn't just about reading a file. In a corporate intranet, this vulnerability turns the web server into a pivot point.

Scenario A: The Cloud Breach As described above, stealing cloud credentials allows the attacker to persist in the cloud environment, potentially escalating to full account takeover depending on the IAM role permissions attached to the web server.

Scenario B: The Internal Probe Is your database server exposing a REST API on an internal IP? Is there a vulnerable Jenkins instance running on port 8080 on the same LAN? The attacker can use the Umbraco server to port scan the internal network (baseUrl=http://192.168.1.5:8080). If the internal service returns HTTP responses (even errors), the attacker can map out the network topology without ever passing a firewall.

This bypasses traditional perimeter firewalls because the traffic originates from the trusted web server.

The Mitigation: Stop the Bleeding

If you are running Umbraco v8.14.1, update immediately. The patch was released in versions v8.14.2+ and v8.15.0+.

Developer Lessons:

  1. Never Trust Input for Outbound Requests: If you need to fetch data from a remote server, define that remote server in your web.config or appsettings.json. Do not let the frontend tell the backend where to connect.
  2. Allowlisting is King: If you must allow dynamic URLs, validate the host against a strict allowlist. Do not use blacklists (e.g., trying to block "localhost" is easily bypassed with 127.0.0.1, 0.0.0.0, or DNS rebinding).
  3. Network Segmentation: Why can your web server talk to the entire internal network? Configure egress filtering on your firewall to only allow outbound connections to necessary services (e.g., your database, specific 3rd party APIs). Block access to 169.254.169.254 if the application doesn't need it.

Technical Appendix

CVSS Score
6.9/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:L/SI:L/SA:L

Affected Systems

Umbraco CMS v8.14.1Umbraco Cloud (prior to July 2021 patch)

Affected Versions Detail

Product
Affected Versions
Fixed Version
Umbraco CMS
Umbraco
= 8.14.18.14.2
AttributeDetail
CWE IDCWE-918
Attack VectorNetwork
CVSS v4.06.9 (Medium)
AuthenticationRequired (BackOffice)
ImpactConfidentiality (High), Integrity (Low)
Exploit StatusPoC Available
CWE-918
Server-Side Request Forgery (SSRF)

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

Vulnerability Timeline

Vulnerability discovered by researcher NgoAnhDuc
2021-07-05
Umbraco v8.14.2 released with fix
2021-07-27
PoC published on Exploit-DB
2021-10-29
Official CVE Record Updated
2026-01-15

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.