Preface: Speaking of sandboxes, our minds may reflexively think of the picture above and become instantly interested, but unfortunately this article does not involve "Minecraft" (an old cover party). The following article will gradually introduce the sandbox of "Browser World". 1. What is a sandbox? In computer security, a For example, the following scenarios involve the abstract concept of sandbox:
2. What are the application scenarios of sandbox?The above introduces some relatively macro sandbox scenarios. In fact, there are many scenarios in daily development that require the application of such a mechanism:
In short, as long as we encounter untrusted third-party code, we can use sandbox to isolate the code to ensure the stable operation of external programs. If untrusted code is executed without any processing, the most obvious side effect/harm in the front end is pollution and tampering of the global // Sub-application code window.location.href = 'www.diaoyu.com' Object.prototype.toString = () => { console.log('You are a fool :)') } document.querySelectorAll('div').forEach(node => node.classList.add('hhh')) sendRequest(document.cookie) ... 3. How to implement a JS sandboxTo implement a sandbox, it is actually necessary to develop a program execution mechanism, under the action of this mechanism, the operation of the program inside the sandbox will not affect the operation of the external program. 3.1 The simplest sandboxTo achieve this effect, the most direct idea is that all variables accessed in the program come from a reliable or autonomous context environment instead of taking values from the global execution environment. Then, to achieve that all variables are accessed from a reliable context environment, We need to construct a scope for the program to be executed: //Execution context object const ctx = func: variable => { console.log(variable) }, foo: 'foo' } // The simplest sandbox function poorestSandbox(code, ctx) { eval(code) // constructs a function scope for executing the program} // Program to be executed const code = ` ctx.foo = 'bar' ctx.func(ctx.foo) ` poorestSandbox(code, ctx) // bar Such a sandbox requires the source program to add the prefix of the execution context object when obtaining any variable, which is obviously very unreasonable because we have no way to control the behavior of third parties. Is there a way to remove this prefix? 3.2 A very simple sandbox (With) Using the //Execution context object const ctx = { func: variable => { console.log(variable) }, foo: 'foo' } // Very Poor Sandbox function veryPoorSandbox(code, ctx) { with(ctx) { // Add with eval(code) } } // Program to be executed const code = ` foo = 'bar' func(foo) ` veryPoorSandbox(code, ctx) // bar This achieves the effect that variables in the executing program are searched in the context provided by the sandbox before the external execution environment. The problem is that when a variable is not found in the provided context object, the code will still search up the scope chain layer by layer. Such a sandbox still cannot control the execution of the internal code. We want the code in the sandbox to only look for variables in the manually provided context object, and to report an error or return 3.3 Not so simple sandbox (With + Proxy) To solve the above problems, we use a new feature of The get and set methods in Since has will intercept all variable accesses in the // Construct a with to wrap the code to be executed and return a function instance of the with code block function withedYourCode(code) { code = 'with(globalObj) {' + code + '}' return new Function('globalObj', code) } // Whitelist of global scopes that can be accessed const access_white_list = ['Math', 'Date'] // Program to be executed const code = ` Math.random() location.href = 'xxx' func(foo) ` //Execution context object const ctx = { func: variable => { console.log(variable) }, foo: 'foo' } // Proxy object of execution context object const ctxProxy = new Proxy(ctx, { has: (target, prop) => { // has can intercept access to any property in the with code block if (access_white_list.includes(prop)) { // In the accessible whitelist, you can continue to search upwards return target.hasOwnProperty(prop) } if (!target.hasOwnProperty(prop)) { throw new Error(`Invalid expression - ${prop}! You can not do that!`) } return true } }) // Not so poor sandbox function littlePoorSandbox(code, ctx) { withedYourCode(code).call(ctx, ctx) // point this to the manually constructed global proxy object} littlePoorSandbox(code, ctxProxy) // Uncaught Error: Invalid expression - location! You can not do that! At this point, many relatively simple scenarios can be covered ( This leads to another question: how to let the subroutine use all global objects without affecting the external global state? 3.4 Natural high-quality sandbox (iframe) When I heard the above question, I immediately called myself an expert. Imagine a scenario like this: there are multiple sandbox windows in a page, one of which needs to share some global states with the main page (eg: when you click the browser back button, the sub-application will also return to the previous level), and another sandbox needs to share some other global states with the main page (eg: share cookie login state). Although the browser provides 3.5 should be able to use the sandbox (With + Proxy + iframe)In order to achieve the above scenario, we can stitch the above methods together:
//Sandbox global proxy object class class SandboxGlobalProxy { constructor(sharedState) { // Create an iframe object and take out the native browser global object as the global object of the sandbox const iframe = document.createElement('iframe', {url: 'about:blank'}) document.body.appendChild(iframe) const sandboxGlobal = iframe.contentWindow // Global object of sandbox runtime return new Proxy(sandboxGlobal, { has: (target, prop) => { // has can intercept access to any property in the with code block if (sharedState.includes(prop)) { // If the property exists in the shared global state, let it search the outer layer along the prototype chain return false } if (!target.hasOwnProperty(prop)) { throw new Error(`Invalid expression - ${prop}! You can not do that!`) } return true } }) } } function maybeAvailableSandbox(code, ctx) { withedYourCode(code).call(ctx, ctx) } const code_1 = ` console.log(history == window.history) // false window.abc = 'sandbox' Object.prototype.toString = () => { console.log('Traped!') } console.log(window.abc) // sandbox ` const sharedGlobal_1 = ['history'] // Global object that you want to share with the external execution environment const globalProxy_1 = new SandboxGlobalProxy(sharedGlobal_1) maybeAvailableSandbox(code_1, globalProxy_1) window.abc // undefined Object.prototype.toString() // [object Object] does not print Traped From the results of the example code, we can see that by leveraging the natural environment isolation advantage of 3.6 Sandbox EscapeSandbox is a security strategy for authors , but it may be a constraint for users. Creative developers try to get rid of this constraint in various ways, which is also called sandbox escape. Therefore, the biggest challenge for a sandbox program is how to detect and prohibit the execution of these unexpected programs. The sandbox implemented above seems to have satisfied our needs. Is it done? In fact, the following operations will affect the environment outside the sandbox and achieve sandbox escape: When accessing an internal property of an object in the sandbox execution context, // When accessing the properties of an object in a sandbox object, some of the above code is omitted const ctx = { window: { parent: {...}, ... } } const code = ` window.parent.abc = 'xxx' ` window.abc // xxx
const code = ` ({}).constructor.prototype.toString = () => { console.log('Escape!') } ` ({}).toString() // Escape! Expected [object Object] 3.7 “Flawless” Sandbox (Customize Interpreter)There are more or less some defects in implementing a sandbox through the above methods. Is there a sandbox that is close to being complete? In fact, many open source libraries are already doing this, that is, analyzing the source program structure to manually control the execution logic of each statement. In this way, both the context of specifying the program runtime and capturing operations that attempt to escape the sandbox control are under control. Implementing such a sandbox is essentially implementing a custom interpreter. function almostPerfectSandbox(code, ctx, illegalOperations) { return myInterpreter(code, ctx, illegalOperations) // custom interpreter } 4. Summary This article mainly introduces the basic concepts and application scenarios of sandboxes and guides you to think about how to implement a JavaScript sandbox. The implementation method of the sandbox is not static, and its goals should be analyzed in combination with specific scenarios. In addition, preventing sandbox escapes is also a long and arduous task, because it is difficult to cover all execution No sandbox is assembled overnight, like Minecraft. 5. Reference References: Source code: https://github.com/vuejs/vue/blob/v2.6.10/src/core/instance/proxy.js You may also be interested in:
|
<<: Docker View JVM Memory Usage
>>: 3 ways to correctly modify the maximum number of connections in MySQL
This question originated from a message on Nugget...
Table of contents 1. Achieve results 2. Backend i...
MySQL View the maximum number of connections and ...
Table of contents Usage scenarios Solution 1. Use...
An event is an action performed by the user or th...
flex layout Definition: The element of Flex layou...
Goal: Create a square whose side length is equal ...
I am a beginner in SQL and thought that the insta...
pthread_create function Function Introduction pth...
This error is often encountered by novices. This ...
Copy code The code is as follows: a:link { font-s...
Table of contents Is setState synchronous or asyn...
Preface The following are the ways to implement L...
Overview of Alibaba Cloud Security Group Sharing ...
I have written an example before, a simple UDP se...