fix: embed.js doesn't expands iframes height (#18301)

also including some refactoring:
- add `// @ts-check`
- use Map to completely avoid prototype pollution
- assign random id to each iframe for reduce chance to brute-force attack, and leak of iframe counts
- check iframe.contentWindow and MessageEvent.source to validate message is coming from correct iframe (it works on latest Chrome/Firefox/Safari but I'm not sure this is allowed by spec)

follow-up of #17420
fix #18299
This commit is contained in:
rinsuki 2022-05-04 10:20:44 +09:00 committed by GitHub
parent a01580f09f
commit 6e736f2452
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 29 additions and 7 deletions

View File

@ -1,6 +1,11 @@
// @ts-check
(function() { (function() {
'use strict'; 'use strict';
/**
* @param {() => void} loaded
*/
var ready = function(loaded) { var ready = function(loaded) {
if (['interactive', 'complete'].indexOf(document.readyState) !== -1) { if (['interactive', 'complete'].indexOf(document.readyState) !== -1) {
loaded(); loaded();
@ -10,26 +15,43 @@
}; };
ready(function() { ready(function() {
var iframes = []; /** @type {Map<number, HTMLIFrameElement>} */
var iframes = new Map();
window.addEventListener('message', function(e) { window.addEventListener('message', function(e) {
var data = e.data || {}; var data = e.data || {};
if (data.type !== 'setHeight' || !iframes[data.id] || window.location.origin !== e.origin || data.id.toString() === '__proto__') { if (typeof data !== 'object' || data.type !== 'setHeight' || !iframes.has(data.id)) {
return; return;
} }
iframes[data.id].height = data.height; var iframe = iframes.get(data.id);
if ('source' in e && iframe.contentWindow !== e.source) {
return;
}
iframe.height = data.height;
}); });
[].forEach.call(document.querySelectorAll('iframe.mastodon-embed'), function(iframe) { [].forEach.call(document.querySelectorAll('iframe.mastodon-embed'), function(iframe) {
// select unique id for each iframe
var id = 0, failCount = 0, idBuffer = new Uint32Array(1);
while (id === 0 || iframes.has(id)) {
id = crypto.getRandomValues(idBuffer)[0];
failCount++;
if (failCount > 100) {
// give up and assign (easily guessable) unique number if getRandomValues is broken or no luck
id = -(iframes.size + 1);
break;
}
}
iframes.set(id, iframe);
iframe.scrolling = 'no'; iframe.scrolling = 'no';
iframe.style.overflow = 'hidden'; iframe.style.overflow = 'hidden';
iframes.push(iframe);
var id = iframes.length - 1;
iframe.onload = function() { iframe.onload = function() {
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
type: 'setHeight', type: 'setHeight',