Có thể chạy Sandbox JavaScript trong trình duyệt không?


142

Tôi tự hỏi liệu có thể chạy sandbox JavaScript trong trình duyệt để ngăn truy cập vào các tính năng thường có sẵn cho mã JavaScript đang chạy trong trang HTML hay không.

Ví dụ: giả sử tôi muốn cung cấp API JavaScript cho người dùng cuối để cho phép họ xác định trình xử lý sự kiện sẽ chạy khi "sự kiện thú vị" xảy ra, nhưng tôi không muốn những người dùng đó truy cập vào các thuộc tính và chức năng của windowđối tượng. Tôi có thể làm điều này?

Trong trường hợp đơn giản nhất, giả sử tôi muốn ngăn người dùng gọi alert. Một vài cách tiếp cận tôi có thể nghĩ ra là:

  • Xác định lại window.alerttoàn cầu. Tôi không nghĩ rằng đây sẽ là một cách tiếp cận hợp lệ vì các mã khác đang chạy trong trang (tức là những thứ không được người dùng ủy quyền trong trình xử lý sự kiện của họ) có thể muốn sử dụng alert.
  • Gửi mã xử lý sự kiện đến máy chủ để xử lý. Tôi không chắc chắn rằng việc gửi mã đến máy chủ để xử lý là cách tiếp cận phù hợp vì trình xử lý sự kiện cần phải chạy trong ngữ cảnh của trang.

Có lẽ một giải pháp trong đó máy chủ xử lý chức năng do người dùng xác định và sau đó tạo ra một cuộc gọi lại để được thực thi trên máy khách sẽ hoạt động? Ngay cả khi phương pháp đó hoạt động có cách nào tốt hơn để giải quyết vấn đề này?

Câu trả lời:


54

Google Caja là một dịch giả nguồn-nguồn-cho phép "cho phép bạn đặt nội tuyến HTML và JavaScript của bên thứ ba không đáng tin cậy trong trang của bạn mà vẫn được bảo mật."


5
Một thử nghiệm nhanh cho thấy Caja không thể bảo vệ trình duyệt khỏi các cuộc tấn công CPU như while (1) {}--- nó chỉ bị treo. Tương tự như vậy a=[]; while (1) { a=[a,a]; }.
David đưa ra

5
Có, việc từ chối dịch vụ nằm ngoài phạm vi: code.google.com/p/google-caja/issues/detail?id=1406
Darius Bacon

32

Hãy nhìn vào ADsafe của Douglas Crockford :

ADsafe giúp an toàn khi đặt mã khách (chẳng hạn như quảng cáo theo kịch bản hoặc tiện ích của bên thứ ba) trên bất kỳ trang web nào. ADsafe định nghĩa một tập hợp con JavaScript đủ mạnh để cho phép mã khách thực hiện các tương tác có giá trị, đồng thời ngăn chặn thiệt hại hoặc sự xâm nhập độc hại hoặc vô tình. Tập hợp con ADsafe có thể được xác minh một cách cơ học bằng các công cụ như JSLint để không cần kiểm tra con người để xem xét mã khách cho an toàn. Tập hợp con ADsafe cũng thực thi các thực hành mã hóa tốt, tăng khả năng mã khách sẽ chạy chính xác.

Bạn có thể xem một ví dụ về cách sử dụng ADsafe bằng cách xem template.htmltemplate.jscác tệp trong kho GitHub của dự án .


Trên trang của họ, tôi thấy không có cách nào sử dụng ADsafe. Không có cách nào để tải nó, không có liên kết đến mã, không có gì. Làm thế nào bạn có thể thử ADsafe?
BT

2
Ngoài ra, nó ngăn chặn mọi quyền truy cập this, điều này hoàn toàn không thể chấp nhận được. Bạn không thể viết javascript tốt mà không cần sử dụng this.
BT

4
@BT Tôi đã viết toàn bộ dự án mà không sử dụng this. Không khó để tránh tham số được đặt tên kém.
soundly_typed

2
@BT Thật ngớ ngẩn khi nói rằng việc hoàn thành các dự án trong thế giới thực là không thể chấp nhận được. Nhưng tôi rất tiếc khi bắt đầu cuộc thảo luận này, và phải rút lui; đây không phải là nơi để thảo luận về những điều như vậy (xin lỗi). Tôi đang trên twitter nếu bạn muốn thảo luận thêm.
soundly_typed

1
@BT (Tôi sẽ tiếp tục vì nó liên quan đến câu hỏi) Bất cứ khi nào bạn chạy mã trong môi trường của người khác, bạn sẽ gặp phải các quy tắc và hạn chế. Tôi sẽ không gọi đó là không thể chấp nhận. Một "nỗi đau ở mông", có thể. Nhưng không thể chấp nhận được. Xét cho cùng, đối với mỗi lần sử dụng this, có một cách tương đương, không tương đương thisđể thực hiện (rốt cuộc đó chỉ là một tham số).
soundly_typed

24

Tôi đã tạo một thư viện hộp cát gọi là jsandbox sử dụng nhân viên web để mã đánh giá hộp cát. Nó cũng có một phương thức nhập để cung cấp dữ liệu mã hộp cát một cách rõ ràng mà nếu không nó sẽ không thể có được.

Sau đây là một ví dụ về 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
      }
  });

+1: Điều này trông thực sự mát mẻ. Làm thế nào an toàn là thực thi mã của người dùng theo cách này?
Konstantin Tarkus

1
Rất an toàn. Kiểm tra thư viện cập nhật trên github .
Eli Gray

1
Dự án này vẫn được duy trì? Tôi thấy nó đã không được cập nhật từ hơn 2 năm qua ...
Yanick Rochon

Tôi thích điều này, ngoại trừ rằng nếu bạn muốn hộp cát nhưng vẫn cho phép mã truy cập để nói jQuery, điều này sẽ thất bại vì nhân viên web không cho phép thao tác DOM.
Rahly 18/03/2015

Xin chào Eli - cảm ơn vì một lib tuyệt vời, bạn có định duy trì nó không? Tôi có một yêu cầu thay đổi để thêm chức năng gỡ lỗi - bằng cách xem nhanh mã có thể thực hiện được. Xin vui lòng cho tôi biết những gì bạn nghĩ?
dùng1514042

8

Tôi nghĩ rằng js.js đáng được đề cập ở đây. Đó là một trình thông dịch JavaScript được viết bằng JavaScript.

Nó chậm hơn khoảng 200 lần so với JS bản địa, nhưng bản chất của nó làm cho nó trở thành một môi trường hộp cát hoàn hảo. Một nhược điểm khác là kích thước của nó - gần 600 kb, có thể được chấp nhận cho máy tính để bàn trong một số trường hợp, nhưng không phải cho thiết bị di động.


7

Như đã đề cập trong các cộng hưởng khác, nó đủ để giam mã trong iframe được đóng hộp cát (mà không gửi mã đến phía máy chủ) và liên lạc bằng tin nhắn. Tôi sẽ đề nghị xem xét một thư viện nhỏ mà tôi đã tạo chủ yếu vì cần cung cấp một số API cho mã không tin cậy, giống như được mô tả trong câu hỏi: có cơ hội để xuất bộ hàm cụ thể ngay vào hộp cát trong đó mã không tin cậy chạy. Và cũng có một bản demo thực thi mã được gửi bởi người dùng trong hộp cát:

http://asvd.github.io/jails/demos/web/console/


4

Tất cả các nhà cung cấp trình duyệt và đặc tả HTML5 đang làm việc hướng tới một thuộc tính hộp cát thực tế để cho phép các iframe hộp cát - nhưng nó vẫn bị giới hạn ở mức độ chi tiết của iframe.

Nói chung, không có mức độ biểu thức chính quy, v.v. có thể vệ sinh một cách an toàn người dùng đã cung cấp JavaScript khi nó suy biến thành vấn đề tạm dừng: - /


2
Bạn có thể giải thích làm thế nào nó thoái hóa đến vấn đề tạm dừng?
hdgarrood

2
Sự bất khả thi về mặt lý thuyết của việc giải quyết vấn đề tạm dừng chỉ thực sự áp dụng cho phân tích mã tĩnh. Hộp cát có thể làm những việc như thi hành giới hạn thời gian để giải quyết vấn đề tạm dừng.
Aviendha

4

Một phiên bản cải tiến của mã hộp cát nhân viên web của @ RyanOHara, trong một tệp duy nhất (không cần thêm eval.jstệp).

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);
    });
    }

Kiểm tra nó:

https://jsfiddle.net/kp0cq6yw/

var promise = safeEval("1+2+3");

promise.then(function (result) {
      alert(result);
      });

Nó sẽ xuất ra 6(được thử nghiệm trong Chrome và Firefox).


2

Một cách xấu xí nhưng có lẽ điều này hiệu quả với bạn, tôi đã lấy tất cả các quả cầu và định nghĩa lại chúng trong phạm vi hộp cát, đồng thời tôi đã thêm chế độ nghiêm ngặt để chúng không thể lấy đối tượng toàn cầu bằng hàm ẩn danh.

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"}

https://gist.github.com/alejandrolechuga/9381781


3
Tầm thường để lấy windowlại từ đó. sandboxcode('console.log((0,eval)("this"))')
Ry-

Tôi sẽ phải tìm ra cách ngăn chặn điều đó
alejandro

@alejandro Bạn đã tìm được cách ngăn chặn điều đó chưa?
Héo

1
Việc triển khai của tôi chỉ cần thêm:function sbx(s,p) {e = eval; eval = function(t){console.log("GOT GOOD")}; sandboxcode(s,p); eval =e}
YoniXw

2
@YoniXw: Tôi hy vọng bạn đã không sử dụng nó cho bất cứ điều gì. Không có cách tiếp cận như thế này sẽ làm việc. (_=>_).constructor('return this')()
Ry-

1

Trình thông dịch Javascript độc lập có nhiều khả năng mang lại một hộp cát mạnh mẽ hơn phiên bản lồng của triển khai trình duyệt dựng sẵn. Ryan đã đề cập đến js.js , nhưng một dự án cập nhật hơn là JS-Interpreter . Các tài liệu bao gồm làm thế nào để hiển thị các chức năng khác nhau cho trình thông dịch, nhưng phạm vi của nó rất hạn chế.


1

Kể từ năm 2019, vm2 có vẻ như là giải pháp phổ biến nhất và được cập nhật thường xuyên nhất cho vấn đề này.


vm2 không hỗ trợ thời gian chạy trong trình duyệt. Tuy nhiên, nó sẽ hoạt động nếu bạn đang tìm kiếm mã hộp cát trong ứng dụng nodejs.
kevin.g họng

0

Với NISP, bạn sẽ có thể thực hiện đánh giá hộp cát. Mặc dù biểu thức bạn viết không chính xác là một JS, thay vào đó bạn sẽ viết biểu thức s. Lý tưởng cho các DSL đơn giản không yêu cầu lập trình rộng rãi.


-3

1) Giả sử bạn có một mã để thực thi:

var sCode = "alert(document)";

Bây giờ, giả sử bạn muốn thực hiện nó trong một hộp cát:

new Function("window", "with(window){" + sCode + "}")({});

Hai dòng này khi được thực thi sẽ thất bại, vì chức năng "cảnh báo" không có sẵn từ "hộp cát"

2) Và bây giờ bạn muốn hiển thị một thành viên của đối tượng cửa sổ với chức năng của bạn:

new Function("window", "with(window){" + sCode + "}")({
    'alert':function(sString){document.title = sString}
});

Thật vậy, bạn có thể thêm dấu ngoặc kép thoát và thực hiện đánh bóng khác, nhưng tôi đoán ý tưởng là rõ ràng.


7
Không có vô số cách khác để có được tại đối tượng toàn cầu? Ví dụ, trong một hàm được gọi là sử dụng func.apply (null) "this" sẽ là đối tượng cửa sổ.
mbarkhau

5
Ví dụ đầu tiên không thất bại, đây là một ví dụ rất không hợp lệ về hộp cát.
Andy E

1
var sCode = "this.alert ('FAIL')";
Leonard Pauli

-4

JavaScript người dùng này đến từ đâu?

Bạn không thể làm gì nhiều về việc người dùng nhúng mã vào trang của bạn và sau đó gọi nó từ trình duyệt của họ (xem Greasemonkey, http://www.greasespot.net/ ). Nó chỉ là một cái gì đó trình duyệt làm.

Tuy nhiên, nếu bạn lưu trữ tập lệnh trong cơ sở dữ liệu, sau đó truy xuất tập lệnh và eval (), sau đó bạn có thể dọn sạch tập lệnh trước khi chạy.

Ví dụ về mã loại bỏ tất cả các cửa sổ. và tài liệu. người giới thiệu:

 eval(
  unsafeUserScript
    .replace(/\/\/.+\n|\/\*.*\*\/, '') // Clear all comments
    .replace(/\s(window|document)\s*[\;\)\.]/, '') // removes window. or window; or window)
 )

Điều này cố gắng ngăn những điều sau đây được thực thi (không được kiểm tra):

window.location = 'http://mydomain.com';
var w = window  ;

Có rất nhiều hạn chế bạn sẽ phải áp dụng cho tập lệnh người dùng không an toàn. Thật không may, không có 'thùng chứa hộp cát' có sẵn cho JavaScript.


2
Nếu ai đó đang cố làm điều gì đó độc hại, một regex đơn giản chỉ không thể làm điều đó - hãy lấy (function () {this ["loca" + "tion"] = " example.com ";}) () Nói chung nếu bạn không thể tin tưởng người dùng của bạn (đó là trường hợp với bất kỳ trang web nào mà mọi người tùy ý có thể thêm nội dung) chặn tất cả js là cần thiết.
olliej

Tôi đã sử dụng một cái gì đó tương tự trong quá khứ. Nó không hoàn hảo, nhưng nó giúp bạn đi gần hết.
Sugendran

olliej, bạn đã đúng về những hạn chế của một kỹ thuật như vậy. Làm thế nào về việc ghi đè các biến toàn cục như <code> var window = null, document = null, this = {}; </ code>?
Dimitry

Dimitry Z, ghi đè các biến này không được phép [trong một số trình duyệt]. Ngoài ra kiểm tra giải pháp của tôi trong danh sách các câu trả lời - nó hoạt động.
Serge Ilinsky

-5

Tôi đã làm việc trên một hộp cát js đơn giản để cho phép người dùng xây dựng các applet cho trang web của tôi. Mặc dù tôi vẫn gặp một số thách thức khi cho phép truy cập DOM (ParentNode sẽ không cho phép tôi giữ mọi thứ an toàn = /), cách tiếp cận của tôi chỉ là xác định lại đối tượng cửa sổ với một số thành viên hữu ích / vô hại của nó, sau đó eval () người dùng mã với cửa sổ được xác định lại này là phạm vi mặc định.

Mã "cốt lõi" của tôi diễn ra như thế này ... (Tôi không hiển thị hoàn toàn;)

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);
            }
        }
    };
}

Vì vậy, tôi có thể ví dụ một Sandbox và sử dụng exec () của nó để chạy mã. Ngoài ra, tất cả các biến được khai báo mới trong mã eval'd cuối cùng sẽ bị ràng buộc với phạm vi thực thi (), do đó sẽ không có xung đột tên hoặc làm rối với mã hiện có.

Mặc dù các đối tượng toàn cầu sẽ vẫn có thể truy cập được, nhưng các đối tượng không xác định đối với mã hộp cát phải được xác định là proxy trong đối tượng Sandbox :: scope.

Hy vọng điều này làm việc cho bạn.


8
Điều này không sandbox gì cả. Mã bị loại bỏ có thể xóa các thành viên và đi đến phạm vi toàn cầu theo cách đó hoặc lấy tham chiếu đến phạm vi toàn cầu bằng cách thực hiện (function () {return this;}) ()
Mike Samuel

-6

Bạn có thể bọc mã người dùng trong một hàm xác định lại các đối tượng bị cấm làm tham số - sau đó chúng sẽ được undefinedgọi là:

(function (alert) {

alert ("uh oh!"); // User code

}) ();

Tất nhiên, những kẻ tấn công thông minh có thể khắc phục điều này bằng cách kiểm tra DOM DOM và tìm một đối tượng không bị ghi đè có chứa tham chiếu đến cửa sổ.


Một ý tưởng khác là quét mã người dùng bằng một công cụ như jslint . Đảm bảo rằng nó được đặt không có các biến đặt trước (hoặc: chỉ các biến bạn muốn), và sau đó nếu bất kỳ toàn cầu nào được đặt hoặc truy cập, đừng để tập lệnh của người dùng được sử dụng. Một lần nữa, có thể dễ bị tổn thương khi đi bộ DOM - các đối tượng mà người dùng có thể xây dựng bằng chữ có thể có các tham chiếu ngầm đến đối tượng cửa sổ có thể được truy cập để thoát khỏi hộp cát.


2
Nếu người dùng đã nhập window.alert thay vì cảnh báo đơn giản, họ sẽ bỏ qua giới hạn đó.
Quentin

@Dorward: có, do đó "các đối tượng bị cấm". wrunby nên quyết định những đối tượng nào người dùng không được phép truy cập và đặt chúng vào danh sách tham số.
John Millikin

Chỉ có một đối tượng - cửa sổ. Nếu bạn không chặn quyền truy cập vào nó, thì mọi thứ đều có sẵn thông qua nó. Nếu bạn chặn nó, thì tập lệnh không thể truy cập bất cứ thứ gì thuộc tính của nó (vì nói cảnh báo thay vì window.alert chỉ ngụ ý cửa sổ.).
Quentin

@Doward: đó không phải là trường hợp bạn sẽ chặn window.alert nhưng cảnh báo vẫn hoạt động, hãy thử nó. Điều này là do cửa sổ cũng là đối tượng toàn cầu. Người ta sẽ cần chặn cửa sổ và bất kỳ thuộc tính hoặc phương pháp nào của cửa sổ mà bạn không muốn mã người dùng truy cập.
AnthonyWJones
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.