Mungkinkah Sandbox JavaScript Berjalan di Browser?

142

Saya bertanya-tanya apakah mungkin untuk menjalankan JavaScript di browser untuk mencegah akses ke fitur yang biasanya tersedia untuk kode JavaScript yang berjalan di halaman HTML.

Sebagai contoh, katakanlah saya ingin memberikan API JavaScript untuk pengguna akhir agar mereka menentukan penangan acara yang akan dijalankan ketika "peristiwa menarik" terjadi, tetapi saya tidak ingin pengguna tersebut mengakses properti dan fungsi windowobjek. Apakah saya dapat melakukan ini?

Dalam kasus paling sederhana, katakanlah saya ingin mencegah panggilan pengguna alert. Beberapa pendekatan yang dapat saya pikirkan adalah:

  • Definisikan ulang window.alertsecara global. Saya tidak berpikir ini akan menjadi pendekatan yang valid karena kode lain yang berjalan di halaman (yaitu hal-hal yang tidak ditulis oleh pengguna dalam event handler mereka) mungkin ingin digunakan alert.
  • Kirim kode event handler ke server untuk diproses. Saya tidak yakin bahwa mengirim kode ke server untuk diproses adalah pendekatan yang tepat karena penangan acara harus berjalan dalam konteks halaman.

Mungkin solusi di mana server memproses fungsi yang ditentukan pengguna dan kemudian menghasilkan panggilan balik yang akan dijalankan pada klien akan bekerja? Bahkan jika pendekatan itu berhasil, adakah cara yang lebih baik untuk menyelesaikan masalah ini?

Walter Rumsby
sumber

Jawaban:

54

Google Caja adalah penerjemah sumber-ke-sumber yang "memungkinkan Anda untuk menempatkan inline HTML dan JavaScript pihak ketiga yang tidak tepercaya di halaman Anda dan tetap aman."

Darius Bacon
sumber
5
Tes cepat menunjukkan bahwa Caja tidak dapat melindungi browser dari serangan CPU seperti while (1) {}--- itu hanya hang. Demikian juga a=[]; while (1) { a=[a,a]; }.
David Diberikan
5
Ya, penolakan layanan berada di luar cakupan: code.google.com/p/google-caja/issues/detail?id=1406
Darius Bacon
32

Lihatlah Douglas Crockford's ADsafe :

ADsafe membuatnya aman untuk meletakkan kode tamu (seperti iklan skrip pihak ketiga atau widget) di halaman web mana pun. ADsafe mendefinisikan subset dari JavaScript yang cukup kuat untuk memungkinkan kode tamu melakukan interaksi yang berharga, sementara pada saat yang sama mencegah kerusakan atau intrusi berbahaya atau tidak sengaja. Subset ADsafe dapat diverifikasi secara mekanis dengan alat-alat seperti JSLint sehingga tidak ada inspeksi manusia yang diperlukan untuk meninjau kode tamu untuk keamanan. Subset ADsafe juga memberlakukan praktik pengkodean yang baik, meningkatkan kemungkinan kode tamu akan berjalan dengan benar.

Anda dapat melihat contoh cara menggunakan ADsafe dengan melihat template.htmldan template.jsfile dalam repositori GitHub proyek .

Simon Lieschke
sumber
Di situs mereka, saya tidak melihat cara menggunakan ADsafe. Tidak ada cara untuk mengunduhnya, tidak ada tautan ke kode, tidak ada. Bagaimana Anda bisa mencoba ADsafe?
BT
2
Juga, ini mencegah akses apa punthis , yang sepenuhnya tidak dapat diterima. Anda tidak dapat menulis javascript yang baik tanpa menggunakan this.
BT
4
@BT Saya sudah menulis seluruh proyek tanpa menggunakan this. Tidak sulit untuk menghindari parameter dengan nama buruk.
soundly_typed
2
@BT Akan konyol untuk mengatakan bahwa menyelesaikan proyek dunia nyata tidak dapat diterima. Tetapi saya menyesal memulai diskusi ini, dan harus mundur; ini bukan tempat untuk membahas hal-hal seperti itu (maaf). Saya di twitter jika Anda ingin membahas lebih lanjut.
soundly_typed
1
@BT (saya akan melanjutkan karena ini relevan dengan pertanyaan) Setiap kali Anda menjalankan kode di lingkungan orang lain, Anda akan mengalami aturan dan batasan. Saya tidak akan menyebut itu tidak dapat diterima. "Rasa sakit di pantat", mungkin. Tapi tidak bisa diterima. Lagi pula, untuk setiap penggunaan this, ada cara yang setara dan setara thisuntuk melakukannya (itu hanya parameter, setelah semua).
soundly_typed
24

Saya membuat perpustakaan sandboxing yang disebut jsandbox yang menggunakan pekerja web untuk sandbox kode dievaluasi. Ini juga memiliki metode input untuk secara eksplisit memberikan data kode sandbox yang tidak bisa didapatkan.

Berikut ini adalah contoh dari 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
      }
  });
Eli Gray
sumber
+1: Ini terlihat sangat keren. Seberapa amankah mengeksekusi kode pengguna dengan cara ini?
Konstantin Tarkus
1
Sangat aman. Lihatlah perpustakaan yang diperbarui di github .
Eli Grey
1
apakah proyek ini masih dipertahankan? Saya melihat itu belum diperbarui sejak lebih dari 2 tahun ...
Yanick Rochon
Saya suka ini, kecuali jika Anda ingin sandbox tetapi masih mengizinkan akses kode untuk mengatakan jQuery, ini akan gagal karena pekerja web tidak mengizinkan manipulasi DOM.
Rahly
Hai Eli - terima kasih untuk lib yang luar biasa, apakah Anda berencana untuk mempertahankannya? Saya telah meminta perubahan untuk menambahkan fungsionalitas debugging - yang dengan cepat melihat kode harus dimungkinkan. Tolong beritahu saya bagaimana menurut anda?
user1514042
8

Saya pikir js.js layak disebutkan di sini. Ini adalah penerjemah JavaScript yang ditulis dalam JavaScript.

Itu sekitar 200 kali lebih lambat dari JS asli, tetapi sifatnya membuatnya menjadi lingkungan kotak pasir yang sempurna. Kelemahan lain adalah ukurannya - hampir 600 kb, yang mungkin dapat diterima untuk desktop dalam beberapa kasus, tetapi tidak untuk perangkat seluler.

gronostaj
sumber
7

Seperti disebutkan dalam respons lain, cukup mem-jailed kode di iframe kotak pasir (tanpa mengirimnya ke sisi server) dan berkomunikasi dengan pesan. Saya akan menyarankan untuk melihat perpustakaan kecil yang saya buat sebagian besar karena kebutuhan untuk menyediakan beberapa API untuk kode yang tidak dipercaya, seperti yang dijelaskan dalam pertanyaan: ada peluang untuk mengekspor set fungsi tertentu langsung ke kotak pasir di mana kode yang tidak dipercaya berjalan. Dan ada juga demo yang mengeksekusi kode yang dikirimkan oleh pengguna di kotak pasir:

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

asvd
sumber
4

Semua vendor browser dan spesifikasi HTML5 sedang bekerja menuju properti kotak pasir yang sebenarnya untuk memungkinkan iframe kotak pasir - tetapi itu masih terbatas pada rincian granularitas iframe.

Secara umum, tidak ada derajat ekspresi reguler, dll. Dapat dengan aman membersihkan pengguna sewenang-wenang yang disediakan JavaScript karena merosot ke masalah penghentian: - /

olliej
sumber
2
Bisakah Anda menjelaskan bagaimana itu merosot menjadi masalah yang terhenti?
hdgarrood
2
Ketidakmungkinan teoritis untuk menyelesaikan masalah penghentian hanya benar-benar berlaku untuk analisis kode statis. Kotak pasir dapat melakukan hal-hal seperti menegakkan batas waktu untuk menangani masalah penghentian.
Aviendha
4

Versi yang ditingkatkan dari kode sandbox pekerja web @ RyanOHara, dalam satu file (tidak eval.jsdiperlukan file tambahan ).

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

Menguji:

https://jsfiddle.net/kp0cq6yw/

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

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

Seharusnya keluaran 6(diuji di Chrome dan Firefox).

MarcG
sumber
2

Cara yang jelek tapi mungkin ini bekerja untuk Anda, saya mengambil semua global dan mendefinisikannya kembali di ruang pasir, juga saya menambahkan mode ketat sehingga mereka tidak bisa mendapatkan objek global menggunakan fungsi anonim.

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

alejandro
sumber
3
Sepele untuk windowkembali dari itu. sandboxcode('console.log((0,eval)("this"))')
Ry-
Saya harus mencari cara untuk mencegahnya
alejandro
@alejandro Apakah Anda menemukan cara untuk mencegahnya?
Wilt
1
Implementasi saya hanya menambahkan:function sbx(s,p) {e = eval; eval = function(t){console.log("GOT GOOD")}; sandboxcode(s,p); eval =e}
YoniXw
2
@YoniXw: Saya harap Anda tidak menggunakannya untuk apa pun. Tidak ada pendekatan seperti ini yang akan berhasil. (_=>_).constructor('return this')()
Ry-
1

Interpreter Javascript independen lebih mungkin menghasilkan kotak pasir yang kuat daripada versi yang dikurung dari implementasi browser bawaan. Ryan sudah menyebutkan js.js , tetapi proyek yang lebih mutakhir adalah JS-Interpreter . The docs mencakup bagaimana untuk mengekspos berbagai fungsi untuk interpreter, tetapi ruang lingkup dinyatakan sangat terbatas.

David Fraser
sumber
1

Pada 2019, vm2 sepertinya solusi yang paling populer dan paling sering diperbarui untuk masalah ini.

Bret Cameron
sumber
vm2 tidak mendukung runtime di browser. Namun, ini seharusnya berfungsi jika Anda mencari kode sandbox di aplikasi nodejs.
kevin.groat
0

Dengan NISP Anda akan dapat melakukan evaluasi kotak pasir. Meskipun ekspresi yang Anda tulis bukanlah JS, namun Anda akan menulis ekspresi-s. Ideal untuk DSL sederhana yang tidak membutuhkan pemrograman yang luas.

Kannan Ramamoorthy
sumber
-3

1) Misalkan Anda memiliki kode untuk dieksekusi:

var sCode = "alert(document)";

Sekarang, misalkan Anda ingin menjalankannya di kotak pasir:

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

Dua baris ini ketika dieksekusi akan gagal, karena fungsi "peringatan" tidak tersedia dari "kotak pasir"

2) Dan sekarang Anda ingin mengekspos anggota objek jendela dengan fungsionalitas Anda:

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

Memang Anda bisa menambahkan tanda kutip yang keluar dan membuat pemolesan lainnya, tapi saya rasa idenya jelas.

Sergey Ilinsky
sumber
7
Apakah tidak ada banyak cara lain untuk mencapai objek global? Misalnya dalam fungsi yang disebut menggunakan func.apply (null) "this" akan menjadi objek jendela.
mbarkhau
5
Contoh pertama tidak gagal, ini adalah contoh sandbox yang sangat tidak valid.
Andy E
1
var sCode = "this.alert ('FAIL')";
Leonard Pauli
-4

Dari mana asal pengguna JavaScript ini?

Tidak banyak yang dapat Anda lakukan tentang pengguna memasukkan kode ke halaman Anda dan kemudian memanggilnya dari browser mereka (lihat Greasemonkey, http://www.greasespot.net/ ). Itu hanya sesuatu yang dilakukan browser.

Namun, jika Anda menyimpan skrip dalam database, lalu mengambilnya dan eval (), maka Anda dapat membersihkan skrip sebelum dijalankan.

Contoh kode yang menghapus semua jendela. dan mendokumentasikan. referensi:

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

Ini mencoba untuk mencegah eksekusi berikut (tidak diuji):

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

Ada banyak batasan yang harus Anda terapkan pada skrip pengguna yang tidak aman. Sayangnya, tidak ada 'wadah kotak pasir' yang tersedia untuk JavaScript.

Dimitry
sumber
2
Jika seseorang mencoba melakukan sesuatu yang berbahaya, regex sederhana tidak dapat melakukannya - take (function () {this ["loca" + "tion"] = " example.com ";}) () Secara umum jika Anda tidak dapat mempercayai pengguna Anda (yang merupakan kasus dengan situs mana pun yang orang sewenang-wenang dapat menambahkan konten) memblokir semua js diperlukan.
olliej
Saya telah menggunakan sesuatu yang serupa di masa lalu. Itu tidak sempurna, tetapi itu membuat Anda sebagian besar jalan ke sana.
Sugendran
olliej, Anda benar tentang keterbatasan teknik semacam itu. Bagaimana dengan menimpa variabel global seperti <code> var window = null, document = null, this = {}; </code>?
Dimitry
Dimitry Z, menimpa variabel-variabel ini tidak diperbolehkan [di beberapa browser]. Juga periksa solusi saya dalam daftar jawaban - itu berhasil.
Sergey Ilinsky
-5

Saya telah mengerjakan kotak pasir js sederhana untuk membiarkan pengguna membuat applet untuk situs saya. Walaupun saya masih menghadapi beberapa tantangan dengan mengizinkan akses DOM (parentNode tidak akan membiarkan saya menjaga semuanya tetap aman = /), pendekatan saya hanya untuk mendefinisikan ulang objek jendela dengan beberapa anggota yang berguna / tidak berbahaya, dan kemudian mengevaluasi () pengguna kode dengan jendela yang didefinisikan ulang ini sebagai ruang lingkup default.

Kode "inti" saya seperti ini ... (Saya tidak menunjukkannya sepenuhnya;)

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

Jadi, saya dapat membuat instance Sandbox dan menggunakan executnya () untuk menjalankan kode. Juga, semua variabel yang dideklarasikan baru dalam kode yang dievalisasi pada akhirnya akan terikat pada lingkup execut (), sehingga tidak akan ada nama yang berbenturan atau mengacaukan kode yang ada.

Meskipun objek global masih dapat diakses, objek yang seharusnya tidak diketahui oleh kode sandboxed harus didefinisikan sebagai proksi dalam objek Sandbox :: scope.

Semoga ini berhasil untuk Anda.


sumber
8
Ini bukan sandbox apa pun. Kode yang diaktifkan dapat menghapus anggota dan sampai ke lingkup global dengan cara itu, atau mengambil referensi ke lingkup global dengan melakukan (function () {return this;}) ()
Mike Samuel
-6

Anda bisa membungkus kode pengguna dalam fungsi yang mendefinisikan ulang objek terlarang sebagai parameter - ini akan undefinedketika dipanggil:

(function (alert) {

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

}) ();

Tentu saja, penyerang yang pandai dapat menyiasatinya dengan memeriksa DOM Javascript dan menemukan objek yang tidak ditimpa yang berisi referensi ke jendela.


Gagasan lain adalah memindai kode pengguna menggunakan alat seperti jslint . Pastikan itu diatur untuk tidak memiliki variabel yang telah ditetapkan (atau: hanya variabel yang Anda inginkan), dan kemudian jika ada global ditetapkan atau diakses jangan biarkan skrip pengguna digunakan. Sekali lagi, mungkin rentan untuk berjalan DOM - objek yang dapat dibangun pengguna menggunakan literal mungkin memiliki referensi implisit ke objek jendela yang dapat diakses untuk keluar dari kotak pasir.

John Millikin
sumber
2
Jika pengguna masuk ke window.alert bukannya lansiran biasa, mereka akan melewati batas itu.
Quentin
@Dorward: ya, maka "benda terlarang". wrunsby harus memutuskan objek apa yang tidak boleh diakses pengguna, dan menempatkannya di daftar parameter.
John Millikin
Hanya ada satu objek - jendela. Jika Anda tidak memblokir akses ke sana, maka semuanya tersedia melaluinya. Jika Anda benar-benar memblokirnya, maka skrip tidak dapat mengakses properti apa pun (karena mengatakan lansiran alih-alih window.alert hanya menyiratkan jendela.).
Quentin
@ Downard: itu bukan kasus Anda akan memblokir window.alert tetapi peringatan akan tetap berfungsi, cobalah. Ini karena jendela juga merupakan objek global. Seseorang harus memblokir jendela dan properti atau metode apa pun dari jendela yang tidak Anda inginkan untuk diakses kode pengguna.
AnthonyWJones