[英]Is it possible to sandbox JavaScript running in the browser?
我想知道是否有可能對在瀏覽器中運行的 JavaScript 進行沙箱化,以防止訪問通常可用於在 HTML 頁面中運行的 JavaScript 代碼的功能。
例如,假設我想為最終用戶提供一個 JavaScript API,讓他們定義在“有趣的事件”發生時要運行的事件處理程序,但我不希望這些用戶訪問window
對象的屬性和函數。 我能做到嗎?
在最簡單的情況下,假設我想阻止用戶調用alert
。 我能想到的幾種方法是:
window.alert
。 我認為這不是一種有效的方法,因為在頁面中運行的其他代碼(即,不是由用戶在其事件處理程序中編寫的內容)可能想要使用alert
。也許服務器處理用戶定義的函數然后生成要在客戶端上執行的回調的解決方案會起作用嗎? 即使這種方法有效,有沒有更好的方法來解決這個問題?
Google Caja是一個源到源的翻譯器,它“允許您將不受信任的第三方 HTML 和 JavaScript 內嵌在您的頁面中,並且仍然安全。”
看看Douglas Crockford 的 ADsafe :
ADsafe 可以安全地在任何網頁上放置訪客代碼(例如第三方腳本廣告或小部件)。 ADsafe 定義了 JavaScript 的一個子集,它足夠強大,允許訪客代碼執行有價值的交互,同時防止惡意或意外損壞或入侵。 ADsafe 子集可以通過 JSLint 等工具進行機械驗證,因此無需人工檢查即可檢查訪客代碼的安全性。 ADsafe 子集還強制執行良好的編碼實踐,增加來賓代碼正確運行的可能性。
您可以通過查看項目的 GitHub 存儲庫中的template.html
和template.js
文件來查看如何使用 ADsafe 的示例。
我創建了一個名為jsandbox的沙盒庫,它使用網絡工作者來沙盒評估代碼。 它還有一個輸入方法,用於顯式提供沙盒代碼數據,否則無法獲得。
以下是該 API 的示例:
jsandbox
.eval({
code : "x=1;Math.round(Math.pow(input, ++x))",
input : 36.565010597564445,
callback: function(n) {
console.log("number: ", n); // number: 1337
}
}).eval({
code : "][];.]\\ (*# ($(! ~",
onerror: function(ex) {
console.log("syntax error: ", ex); // syntax error: [error object]
}
}).eval({
code : '"foo"+input',
input : "bar",
callback: function(str) {
console.log("string: ", str); // string: foobar
}
}).eval({
code : "({q:1, w:2})",
callback: function(obj) {
console.log("object: ", obj); // object: object q=1 w=2
}
}).eval({
code : "[1, 2, 3].concat(input)",
input : [4, 5, 6],
callback: function(arr) {
console.log("array: ", arr); // array: [1, 2, 3, 4, 5, 6]
}
}).eval({
code : "function x(z){this.y=z;};new x(input)",
input : 4,
callback: function(x) {
console.log("new x: ", x); // new x: object y=4
}
});
正如其他回復中提到的,將代碼監禁在沙盒 iframe 中(無需將其發送到服務器端)並與消息進行通信就足夠了。
我建議看看我創建的一個小庫,主要是因為需要為不受信任的代碼提供一些 API,就像問題中描述的那樣:有機會將特定的函數集導出到沙箱中不受信任的代碼運行。 還有一個演示,它在沙箱中執行用戶提交的代碼:
RyanOHara 的網絡工作者沙箱代碼的改進版本,在單個文件中(不需要額外的eval.js
文件)。
function safeEval(untrustedCode)
{
return new Promise(function (resolve, reject)
{
var blobURL = URL.createObjectURL(new Blob([
"(",
function ()
{
var _postMessage = postMessage;
var _addEventListener = addEventListener;
(function (obj)
{
"use strict";
var current = obj;
var keepProperties =
[
// Required
'Object', 'Function', 'Infinity', 'NaN', 'undefined', 'caches', 'TEMPORARY', 'PERSISTENT',
// Optional, but trivial to get back
'Array', 'Boolean', 'Number', 'String', 'Symbol',
// Optional
'Map', 'Math', 'Set',
];
do
{
Object.getOwnPropertyNames(current).forEach(function (name)
{
if (keepProperties.indexOf(name) === -1)
{
delete current[name];
}
});
current = Object.getPrototypeOf(current);
}
while (current !== Object.prototype)
;
})(this);
_addEventListener("message", function (e)
{
var f = new Function("", "return (" + e.data + "\n);");
_postMessage(f());
});
}.toString(),
")()"],
{type: "application/javascript"}));
var worker = new Worker(blobURL);
URL.revokeObjectURL(blobURL);
worker.onmessage = function (evt)
{
worker.terminate();
resolve(evt.data);
};
worker.onerror = function (evt)
{
reject(new Error(evt.message));
};
worker.postMessage(untrustedCode);
setTimeout(function ()
{
worker.terminate();
reject(new Error('The worker timed out.'));
}, 1000);
});
}
測試一下:
https://jsfiddle.net/kp0cq6yw/
var promise = safeEval("1+2+3");
promise.then(function (result) {
alert(result);
});
它應該輸出6
(在 Chrome 和 Firefox 中測試)。
我認為js.js在這里值得一提。 它是一個用 JavaScript 編寫的 JavaScript 解釋器。
它比原生 JavaScript 慢大約 200 倍,但它的性質使它成為一個完美的沙盒環境。 另一個缺點是它的大小——將近 600 KB,這在某些情況下對於台式機來說是可以接受的,但對於移動設備來說則不然。
所有瀏覽器供應商和 HTML5 規范都在努力實現一個實際的沙箱屬性,以允許沙箱 iframe —— 但它仍然限於 iframe 粒度。
一般來說,沒有任何程度的正則表達式等可以安全地清理任意用戶提供的 JavaScript,因為它退化為停機問題:-/
與內置瀏覽器實現的籠子版本相比,獨立的 JavaScript 解釋器更有可能產生強大的沙箱。
Ryan已經提到了js.js ,但一個更新的項目是JS-Interpreter 。 該文檔涵蓋了如何向解釋器公開各種功能,但其范圍非常有限。
一種丑陋的方式,但也許這對您有用:
我獲取了所有全局變量並在沙箱范圍內重新定義了它們,並且我添加了嚴格模式,因此它們無法使用匿名函數獲取全局對象。
function construct(constructor, args) {
function F() {
return constructor.apply(this, args);
}
F.prototype = constructor.prototype;
return new F();
}
// Sanboxer
function sandboxcode(string, inject) {
"use strict";
var globals = [];
for (var i in window) {
// <--REMOVE THIS CONDITION
if (i != "console")
// REMOVE THIS CONDITION -->
globals.push(i);
}
globals.push('"use strict";\n'+string);
return construct(Function, globals).apply(inject ? inject : {});
}
sandboxcode('console.log( this, window, top , self, parent, this["jQuery"], (function(){return this;}()));');
// => Object {} undefined undefined undefined undefined undefined undefined
console.log("return of this", sandboxcode('return this;', {window:"sanboxed code"}));
// => Object {window: "sanboxed code"}
截至 2019 年, vm2看起來是在 Node.js 中運行 JavaScript 的最流行和最定期更新的解決方案。 我不知道前端解決方案。
使用NISP,您將能夠進行沙盒評估。
盡管您編寫的表達式不完全是 JavaScript 代碼,但您將編寫S-expressions 。 它非常適合不需要大量編程的簡單DSL 。
假設你有代碼要執行:
var sCode = "alert(document)";
現在,假設您想在沙箱中執行它:
new Function("window", "with(window){" + sCode + "}")({});
這兩行在執行時會失敗,因為“沙箱”中沒有“警報”功能
現在你想用你的功能公開一個 window 對象的成員:
new Function("window", "with(window){" + sCode + "}")({ 'alert':function(sString){document.title = sString} });
確實,您可以添加引號轉義並進行其他潤色,但我想這個想法很清楚。
這個用戶 JavaScript 代碼來自哪里?
對於用戶將代碼嵌入您的頁面然后從他們的瀏覽器調用它,您無能為力(請參閱Greasemonkey )。 這只是瀏覽器所做的事情。
但是,如果您將腳本存儲在數據庫中,然后檢索它並 eval() 它,那么您可以在運行之前清理腳本。
刪除所有窗口的代碼示例。 和文件。 參考:
eval(
unsafeUserScript
.replace(/\/\/.+\n|\/\*.*\*\/, '') // Clear all comments
.replace(/\s(window|document)\s*[\;\)\.]/, '') // Removes window. Or window; or window)
)
這試圖防止以下內容被執行(未測試):
window.location = 'http://example.com';
var w = window;
您必須對不安全的用戶腳本應用很多限制。 不幸的是,沒有任何可用於 JavaScript 的“沙箱容器”。
我一直在開發一個簡單的 JavaScript 沙箱,讓用戶為我的網站構建小程序。 盡管我在允許 DOM 訪問方面仍然面臨一些挑戰(parentNode 只是不會讓我保持安全=/),但我的方法只是用一些有用/無害的成員重新定義 window 對象,然后 eval() 用戶將此重新定義的窗口作為默認范圍的代碼。
我的“核心”代碼是這樣的......(我沒有完全展示它;)
function Sandbox(parent){
this.scope = {
window: {
alert: function(str){
alert("Overriden Alert: " + str);
},
prompt: function(message, defaultValue){
return prompt("Overriden Prompt:" + message, defaultValue);
},
document: null,
.
.
.
.
}
};
this.execute = function(codestring){
// Here some code sanitizing, please
with (this.scope) {
with (window) {
eval(codestring);
}
}
};
}
所以,我可以實例化一個 Sandbox 並使用它的execute()函數來運行代碼。 此外,在 eval 代碼中所有新聲明的變量最終都將綁定到 execute() 范圍,因此不會出現名稱沖突或與現有代碼混淆。
盡管全局對象仍可訪問,但那些對沙盒代碼仍然未知的對象必須在 Sandbox::scope 對象中定義為代理。
您可以將用戶的代碼包裝在一個函數中,該函數將禁用的對象重新定義為參數——這些在調用時將是undefined
:
(function (alert) {
alert ("uh oh!"); // User code
}) ();
當然,聰明的攻擊者可以通過檢查 JavaScript DOM 並找到一個包含對窗口的引用的非覆蓋對象來解決這個問題。
另一個想法是使用JSLint 之類的工具掃描用戶的代碼。 確保它設置為沒有預設變量(或:只有您想要的變量),然后如果設置或訪問了任何全局變量,請不要使用用戶的腳本。 同樣,它可能容易受到遍歷 DOM 的影響——用戶可以使用文字構造的對象可能具有對可以訪問以逃避沙箱的 window 對象的隱式引用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.