思路是把 iframe 作爲沙箱環境,讓 eval 在 iframe 中執行。
以 Chrome Manifest V2 爲例。V3 可參考 Using eval in Chrome extensions - Chrome Developers。
1. 在 manifest 文件中列出沙箱 html
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
2. 加載沙箱 html
比如在 Manifest V2 中,可以將其放在背景頁中(Manifest V3 取消了背景頁):
<!-- background.html -->
<body>
<iframe id="theFrame" src="sandbox.html" style="display: none"></iframe>
</body>
3. 在沙箱 html 中執行 eval 等特殊操作
<!DOCTYPE html>
<html>
<body>
<script>
// 接收消息
window.addEventListener('message', async function (event) {
const message = event.data
let result = ''
if (message) {
switch (message.command) {
// 根據所收到消息的 command 值決定執行流程
case 'eval':
try {
result = eval(message.expression)
console.log(result)
} catch (error) {
event.source?.postMessage({ error, command: message.command }, event.origin)
return;
}
break
}
}
// 傳回消息給消息發起方
event.source?.postMessage({ result, command: message.command }, event.origin)
});
</script>
</body>
</html>
4. 傳遞消息給沙箱 html
因爲 sandbox 放在了 background.html 中,所以在 background.html 的 js 腳本中添加如下內容:
// 發送 eval 消息到 iframe
function sandboxEval(expression) {
const iframe = document.getElementById('theFrame')
const message = {
command: 'eval',
expression
};
iframe?.contentWindow?.postMessage(message, '*')
}
// 接收消息
window.addEventListener('message', function(event){
const message = event.data
if (message) {
switch (message.command) {
// 接收來自 iframe 的 eval 結果
case 'eval':
if (message.error) {
console.error(message.error)
} else {
console.log(message.result)
}
break
}
}
})
// 調用函數進行測試
sandboxEval('1 + 1')
這樣就間接實現了調用 eval 函數。
在模板引擎比如 Nunjucks、Handlebars 中,通常會使用到 eval 等類似比較“危險”的函數,遇到無法在瀏覽器擴展中使用這些庫時可以類似解決。
參考:Using eval in Chrome extensions - Chrome Developers、javascript - JS templating in google chrome extension(manifest v2) - Stack Overflow