Skip to content

Commit da225d7

Browse files
authored
Avoid Cross Site Scripting Vectors To/From the Simulator Iframe (microsoft#7473)
* Check the origin of MessageEvents in the SimulatorDriver * Move origin check into a helper function * Move helper function into runtime.ts * Have the embedded frame check the origin of th received message * Use the simUrl when posting messages to the sim iframe * Use the simUrl when creating or receiving messages from the sim iframe * Add logging to messageOriginExpected * Using * when posting to the sim iframe when running on local host * Don't use the simUrl when posting messages to the parent window * Use the SimDriver's origin when passing messages onto the parent window * Don't test the origin in the simulator iframe * Check only the main domain when receiving messages from the sim * Include the SimDriver origin when loading a sim iframe * Add more logging * Use the parent origin when sending messages to the sim driver's parent window * Only set the parent origin on the sim driver when it has a parent window * Allow message from the sim domain, the current origin, and the given parent window's origin * Include the parent origin in the streamer page so editors can send/receive message from the parent origin. * Remove extra logging * Remove the parent origin from the simulator search params When we decide to check the origin of received messages in the simulator, as well as when we post messages back to the simulator frame's parent window, then we'll should put this back. That way, we'll be able to do something with it, vs leaving it and having it do nothing. * Remove empty line change from embed.ts * Encode and decode the parentOrigin URI * Use !== not != when checking the port and protocol of the given origin * Don't include | null in type since strict null checking is disabled * Use the current origin when sending messages to dependant editors * Use a set of expected origins in the SimulatorDriver * Minor fixes found in PR comments * Remove use of pxt.reportException in the pxtsim This is mostly due to size considerations as including the pxtlib in pxtsim will increase the file size. Perhaps the analytics portion of the lib can split out of pxtlib so other parts of the project that want to use it can. * Use an array instead of a Set for the expected origins
1 parent 73ffa09 commit da225d7

6 files changed

Lines changed: 54 additions & 13 deletions

File tree

docs/multi.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
var selectRight = document.getElementById("selectright");
7979
var divider = document.getElementById("divider");
8080
var localhost = /localhost=1/.test(window.location.href)
81-
var flags = "?nestededitorsim=1&editorlayout=ide&nosandbox=1";
81+
var flags = "?nestededitorsim=1&editorlayout=ide&nosandbox=1&parentOrigin=" + encodeURIComponent(window.location.origin);
8282
var ratio = .5;
8383
var dividerWidth = 14;
8484

@@ -167,7 +167,7 @@
167167

168168
setWidths();
169169
handleHash();
170-
})();
170+
})();
171171
</script>
172172
<!-- @include tracking.html -->
173173
</body>

docs/static/streamer/script.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ function onYouTubeIframeAPIReady() {
730730
loadStyle();
731731
return;
732732
}
733-
let url = `${editorConfig.url}?editorLayout=ide&nosandbox=1`;
733+
let url = `${editorConfig.url}?editorLayout=ide&nosandbox=1&parentOrigin=${encodeURIComponent(window.location.origin)}`;
734734
if (config.multiEditor)
735735
url += `&nestededitorsim=1`;
736736
if (hash)

docs/static/streamer/streamer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,7 @@ function onYouTubeIframeAPIReady() {
876876
return;
877877
}
878878

879-
let url = `${editorConfig.url}?editorLayout=ide&nosandbox=1`;
879+
let url = `${editorConfig.url}?editorLayout=ide&nosandbox=1&parentOrigin=${encodeURIComponent(window.location.origin)}`;
880880
if (config.multiEditor)
881881
url += `&nestededitorsim=1`;
882882
if (hash)

pxtsim/simdriver.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace pxsim {
1818
// instead of spanning multiple simulators,
1919
// dispatch messages to parent window
2020
nestedEditorSim?: boolean;
21+
parentOrigin?: string
2122
}
2223

2324
export enum SimulatorState {
@@ -76,12 +77,23 @@ namespace pxsim {
7677
public state = SimulatorState.Unloaded;
7778
public hwdbg: HwDebugger;
7879
private _dependentEditors: Window[];
80+
private _allowedOrigins: string[] = [];
7981

8082
// we might "loan" a simulator when the user is recording
8183
// screenshots for sharing
8284
private loanedSimulator: HTMLDivElement;
8385

8486
constructor(public container: HTMLElement, public options: SimulatorDriverOptions = {}) {
87+
this._allowedOrigins.push(window.location.origin);
88+
if (options.parentOrigin) {
89+
this._allowedOrigins.push(options.parentOrigin)
90+
}
91+
try {
92+
const simUrl = new URL(this.getSimUrl())
93+
this._allowedOrigins.push(simUrl.origin)
94+
} catch (e) {
95+
console.error(`Invalid sim url ${this.getSimUrl()}`)
96+
}
8597
}
8698

8799
isDebug() {
@@ -246,6 +258,10 @@ namespace pxsim {
246258
return frames;
247259
}
248260

261+
private getSimUrl(): string {
262+
return this.options.simUrl || ((window as any).pxtConfig || {}).simUrl || "/sim/simulator.html"
263+
}
264+
249265
public postMessage(msg: pxsim.SimulatorMessage, source?: Window) {
250266
if (this.hwdbg) {
251267
this.hwdbg.postMessage(msg)
@@ -255,19 +271,23 @@ namespace pxsim {
255271
const broadcastmsg = msg as pxsim.SimulatorBroadcastMessage;
256272
const depEditors = this.dependentEditors();
257273
let frames = this.simFrames();
274+
const simUrl = U.isLocalHost() ? "*" : this.getSimUrl();
258275
if (source && broadcastmsg && !!broadcastmsg.broadcast) {
259276
// the editor is hosted in a multi-editor setting
260277
// don't start extra frames
261278
const parentWindow = window.parent && window.parent !== window.window
262279
? window.parent : window.opener;
263280
if (this.options.nestedEditorSim && parentWindow) {
264281
// if message comes from parent already, don't echo
265-
if (source !== parentWindow)
266-
parentWindow.postMessage(msg, "*");
282+
if (source !== parentWindow) {
283+
const parentOrigin = this.options.parentOrigin || window.location.origin
284+
parentWindow.postMessage(msg, parentOrigin);
285+
}
267286
} else if (depEditors) {
268287
depEditors.forEach(w => {
269288
if (source !== w)
270-
w.postMessage(msg, "*")
289+
// dependant editors should be in the same origin
290+
w.postMessage(msg, window.location.origin)
271291
});
272292
} else {
273293
// start secondary frame if needed
@@ -288,7 +308,7 @@ namespace pxsim {
288308
// frame not in DOM
289309
if (!frame.contentWindow) continue;
290310

291-
frame.contentWindow.postMessage(msg, "*");
311+
frame.contentWindow.postMessage(msg, simUrl);
292312

293313
// don't start more than 1 recorder
294314
if (msg.type == 'recorder'
@@ -307,9 +327,8 @@ namespace pxsim {
307327
frame.allowFullscreen = true;
308328
frame.setAttribute('allow', 'autoplay');
309329
frame.setAttribute('sandbox', 'allow-same-origin allow-scripts');
310-
let simUrl = this.options.simUrl || ((window as any).pxtConfig || {}).simUrl || "/sim/simulator.html"
311330
frame.className = 'no-select'
312-
frame.src = simUrl + '#' + frame.id;
331+
frame.src = this.getSimUrl() + '#' + frame.id;
313332
frame.frameBorder = "0";
314333
frame.dataset['runid'] = this.runId;
315334

@@ -601,6 +620,12 @@ namespace pxsim {
601620
if (!this.listener) {
602621
this.listener = (ev: MessageEvent) => {
603622
if (this.hwdbg) return
623+
624+
if (U.isLocalHost()) {
625+
// no-op
626+
} else {
627+
if (!this._allowedOrigins.find(origin => origin === ev.origin)) return
628+
}
604629
this.handleMessage(ev.data, ev.source as Window)
605630
}
606631
window.addEventListener('message', this.listener, false);

webapp/public/multi.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
var divider = document.getElementById("divider");
5454
var localhost = window.location.hostname == "localhost";
5555
var editor = (pxtConfig ? pxtConfig.relprefix : '/').replace(/-*$/, '');
56-
var flags = "?nestededitorsim=1&editorlayout=ide&nosandbox=1";
56+
var flags = "?nestededitorsim=1&editorlayout=ide&nosandbox=1&parentOrigin=" + encodeURIComponent(window.location.origin);
5757
var ratio = .5;
5858
var dividerWidth = 14;
5959

@@ -136,7 +136,7 @@
136136
}
137137
setWidths();
138138
handleHash();
139-
})();
139+
})();
140140
</script>
141141
<!-- @include tracking.html -->
142142
</body>

webapp/src/simulator.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ export function init(root: HTMLElement, cfg: SimulatorConfig) {
4545
root.appendChild(debuggerDiv);
4646

4747
const nestedEditorSim = /nestededitorsim=1/i.test(window.location.href);
48+
let parentOrigin: string = null;
49+
if (window.parent !== window) {
50+
const searchParams = new URLSearchParams(window.location.search);
51+
const origin = searchParams.get("parentOrigin")
52+
53+
// validate the URI
54+
if (!!origin) {
55+
try {
56+
const originUrl = new URL(origin);
57+
parentOrigin = originUrl.origin
58+
} catch (e) {
59+
console.error(`Invalid parent origin: ${origin}`)
60+
}
61+
}
62+
}
4863

4964
let options: pxsim.SimulatorDriverOptions = {
5065
restart: () => cfg.restartSimulator(),
@@ -219,7 +234,8 @@ export function init(root: HTMLElement, cfg: SimulatorConfig) {
219234
},
220235
stoppedClass: pxt.appTarget.simulator && pxt.appTarget.simulator.stoppedClass,
221236
invalidatedClass: pxt.appTarget.simulator && pxt.appTarget.simulator.invalidatedClass,
222-
nestedEditorSim: nestedEditorSim
237+
nestedEditorSim: nestedEditorSim,
238+
parentOrigin: parentOrigin
223239
};
224240
driver = new pxsim.SimulatorDriver(document.getElementById('simulators'), options);
225241
config = cfg

0 commit comments

Comments
 (0)