如何访问沙箱iframe中嵌套iframe的contentWindow.document并控制权限
问题需求与遇到的问题
我需要实现以下嵌套沙箱iframe的需求:
- 创建沙箱化的
<iframe>(frame1):允许运行脚本,但禁止访问父级页面 - frame1内部创建沙箱化iframe(frame2):禁止运行任何JavaScript,但frame1必须能对其进行DOM操作
已知矛盾:如果给frame1设置sandbox="allow-same-origin",frame1确实能访问frame2,但同时也获得了访问父级页面的权限,不符合需求。
我尝试了多种sandbox属性值与iframe源设置的组合,遇到两个核心错误:
- 错误1:
不允许加载本地资源: blob:.... - 错误2:
未捕获DOMException: 无法从'Window'读取命名属性'document':阻止源为"null"的框架访问跨源框架
测试代码片段
var inner = [0,0,0,0] var outer = [0,0,0] var focus = null document.body.innerHTML = [ [0,0],[0,1],[0,2], [1,0],[1,1],[1,2], [2,0],[2,1],[2,2], [3,0],[3,1],[3,2], ].map(([i,o]) => `<button>${i}-${o}</button>`).join('') const container = document.createElement('div') const buttons = [...document.body.children] document.body.append(container) buttons.map(btn => { btn.onclick = reload }) function reload () { if (focus) focus.style = '' const btn = focus = this btn.style = 'background-color: pink;' const text = btn.textContent const [I,O] = text.split('-').map(Number) inner = inner.map((x, i) => i === I) outer = outer.map((x, o) => o === O) container.replaceChildren() spawn(container) } // ----------------------------------------------- async function spawn (element) { const program = 'test' const htmlsrc = `<!DOCTYPE html> <html><head><meta charset="utf-8"></head><body>333</body></html>` const blobsrc = new Blob([htmlsrc], { type: "text/html" }) const hrefsrc = URL.createObjectURL(blobsrc) const datauri = `data:text/html;charset=utf-8,${htmlsrc}` const src_js = ` const iframe = document.createElement('iframe') iframe.setAttribute('sandbox', 'allow-same-origin') const html = \`<!DOCTYPE html> <html><head><meta charset="utf-8"></head><body>333</body></html>\` const blob = new Blob([html], { type: "text/html" }) const href = URL.createObjectURL(blob) const href2 = "${hrefsrc}" const href3 = \`${datauri}\` console.log({ href: href }) console.log({ href2: href2 }) console.log({ href3: href3 }) console.log({ lhref: location.href }) // not allowed to load resource if (${inner[0]}) iframe.setAttribute('src', href2) // no cross origin access: if (${inner[1]}) iframe.setAttribute('src', href) if (${inner[2]}) iframe.setAttribute('srcdoc', html) if (${inner[3]}) iframe.setAttribute('src', href3) iframe.onload = () => { console.log("readonly iframe loaded") const innerDoc = iframe.contentWindow.document console.log(innerDoc.body.innerHTML) } document.body.appendChild(iframe) ` const string = src_js const sandbox = 'allow-scripts' const html = index_html(wrap(string, program)) const src = _2href(_2blob(html), `#${program}`) const srcuri = `data:text/html;base64,${btoa(html)}` if (outer[0]) { const { global, data, port } = await iframer(element, { srcdoc:html, sandbox }) } if (outer[1]) { const { global, data, port } = await iframer(element, { src: srcuri, sandbox }) } if (outer[2]) { const { global, data, port } = await iframer(element, { src, sandbox }) } } // ---------------------------------------------------------------------- function _2blob (html) { return new Blob([html], { type: "text/html" }) } function _2href (blob, query = '') { return URL.createObjectURL(blob) + query } function index_html (source) { return `<!DOCTYPE html> <html> <head><meta charset="utf-8"></head> <body>${source}</body> </html>` } function wrap (source_js, program) { const filepath = `${program || '(anonymous)'}.js` const bootloader_js = `;(async element => { // document.currentScript.remove() const source = element.textContent element.remove() console.log('load: ' + '${program}') eval(source) //# sourceURL=(iojs:bootloader) //# ignoreList=(iojs:bootloader) })(document.querySelector('[type="text"]'))` source_js = `;(async () => { console.log('run: ' + "${filepath}");${source_js} //# sourceURL=${filepath} //# ignoreList=${filepath} })()` return ` <script type="text">${source_js}</`+`script> <script>${bootloader_js}</`+`script> ` } // ---------------------------------------------------------------------- function iframer (element, { src, srcdoc, sandbox = '', timeout } = {}) { const el = document.createElement('div') const sh = el.attachShadow({ mode: 'closed' }) sh.innerHTML = `<iframe sandbox="${sandbox}"></iframe>` const [iframe] = sh.children const { promise, resolve, reject } = Promise.withResolvers() iframe.onload = onload window.addEventListener('message', onmessage) const id = timeout !== undefined ? setTimeout(ontimeout, timeout) : null if (src) iframe.src = src else if (srcdoc) iframe.srcdoc = srcdoc element.append(el) return promise function ontimeout () { window.removeEventListener('message', onmessage) iframe.onload = undefined reject(new Error('iframe timeout')) } function onload () { clearTimeout(id) iframe.onload = undefined window.removeEventListener('message', onmessage) resolve({ global: iframe.contentWindow, data: null, port: null }) } function onmessage (event) { const { source, data = null, ports: [port = null] } = event if (source === iframe.contentWindow) { clearTimeout(id) iframe.onload = undefined window.removeEventListener('message', onmessage) const sorigin = sandbox.includes('allow-same-origin') resolve({ global: sorigin ? iframe.contentWindow : null, data, port }) } } }
内容的提问来源于stack exchange,提问作者serapath
相关产品推荐
相关产品推荐

