JavaScript has no security model. The runtime environments do. This post is a primer on how to think about JavaScript code security in Web browsers and Node.js.
How Browsers Execute JavaScript Code?
JavaScript was created to add interactivity to HTML pages. Web browsers were the first runtime environment for JavaScript code.
When the user visits a Web page, the browser downloads the HTML code of that page and parses it to create the Document Object Model (DOM). The HTML contains information about other assets that need to be downloaded to render the page to the user. This includes stylesheets (CSS), images, other documents to display in frames, and many more.
The type of asset we are most interested in here is JavaScript code. It is also downloaded by the browser from locations referenced in the HTML.
Same-Origin Policy
Users can simultaneously visit many pages in tabs or separate browser windows. JavaScript code downloaded from multiple different sites is executed in the same browser.
One of those sites could be infected or operated by an attacker. Is this a risk? Could malicious code compromise the machine or steal data from other sites the user is browsing?
Browsers protect against this. Each Web site executes JavaScript code in a sandbox. Code downloaded from one Web site cannot read or write data from another site. It also cannot call functions or methods across different sites.
This is called the Same-origin policy (SOP) and it is one of the most fundamental security policies on the Web.
Protecting Code Integrity
Attackers could breach the SOP through the injection of malicious code at the network level, making the injected code appear as coming from the legitimate site. Browsers use the secure HTTPS protocol to ensure JavaScript code is downloaded from the legitimate server and that the code is not tampered with in transit.
JavaScript is often distributed using Content Delivery Networks (CDN). Attackers capable of injecting content into the CDN could also compromise the SOP. Subresource Integrity (SRI) provides an additional level of protection that allows HTML code to be cryptographically bound to JavaScript code to prevent this.
Sandboxing
Sandboxing is difficult to implement. Browsers use isolation mechanisms provided by the hardware and the operating system. JavaScript code from different sites is executed in separate processes.
The code in a sandbox is restricted in what it can do. It cannot directly access devices such as webcams or microphones. The filesystem and the local network are also not directly available.
JavaScript can use those resources only through very limited APIs. This reduces the attack surface. It also allows the browser to always ask the user for explicit permission before uploading files, capturing the webcam, or listening to the user’s microphone.
Node.js vs Browsers
Node.js is a runtime environment for JavaScript based on the V8 engine built for the Google Chrome browser. It allows JavaScript code to be executed outside of the browser, typically on servers.
Node.js does not use the browser sandbox to run JavaScript. Security properties of both execution environments are different:
- Origin. Browsers download the code and Node.js loads the code from local files like other popular programming languages.
- Trust. Browsers treat the code as untrusted and Node.js treats the code with full trust.
- Permissions. Browsers restrict capabilities the code has access to and Node.js grants all the privileges of the operating system account. This includes access to devices, files, and the local network.
Impact on Security
The same JavaScript script or module can be executed in the browser or Node.js. Potential attacks may be different in both environments. The impact of successful exploits may be drastically different. It is very hard to reason about the security of JavaScript code without a specific execution environment in mind.
Browsers
A successful attack on JavaScript code running in the browser impacts a single user. The impact is limited to what the sandbox, browser APIs, and the user’s explicit consent allow for.
Compromised JavaScript script or module runs within the context of an authenticated session of the victim and it can perform actions on behalf of the user. In this scenario, the vulnerable code becomes an attack vector against Web applications the victim has legitimate access to.
Node.js
A successful attack on Node.js programs may impact the entire server the program runs on. The attacker may get access to all the resources the operating system account has access to, potentially leading to a full compromise of the server.
What’s next?
The next post in this series will demonstrate how the dynamic type system may lead to subtle security bugs.
Top comments (0)