📜 ⬆️ ⬇️

Timing attack on Node.js - when time works against you

Imagine a service (or a web application) that gives you a message like “the fifth character of the password you entered is incorrect” in response to your authentication attempt. Looks absurd, right? By providing a potential attacker with this kind of information, we simply give him a chance to “pull off” (pick up, by brute force) the password from the service.

At the same time, this is practically the very event that occurs when, for example, we use the simplest mechanism for comparing the string data type during the verification of passwords or tokens for authentication.


By itself, a “timing attack” or “time attack” is an attack on a system over an open access channel, when an attacker tries to compromise a system by analyzing the time spent on executing algorithms. Each operation (especially mathematical, be it addition, subtraction, exponentiation, etc.) requires a certain time for execution, and this time may vary depending on the input data. With accurate measurements of the time spent on these operations, an attacker can recover the data needed to log on to the system.

Something about JavaScript


Returning to the introduction, the string comparison engine in JavaScript, based on the === operator, works like a normal iteration for strings of the same length, comparing strings with each other by the simplest enumeration of characters, going forward at the same time if the compared pair of characters is identical, and unexpectedly stopping if one of the characters in the pair is different.
')
function isAuthenticated(user, token) { var correctToken = FetchUserTokenFromDB(user); return token === correctToken; } 

Sample unwanted code

This operation is fast, but at the same time unsafe. Studies show that it is easy for an attacker to measure time intervals from 15 to 100 microseconds via the Internet and 100 nanoseconds through a local network. In other words, the attackers are able to use the technique of such tiny time delays, like a hint - which character came up and which one did not. To prevent the development of such scenarios, we should implement a string processing mechanism so that its processing takes the same period of time, regardless of the given password.

For example, applying the logical operation xor for two passwords, while receiving the output 0.

 var mismatch = 0; for (var i = 0; i <a.lenght; ++i) { mismatch | = (a.charCodeat(i)) ^ b.charCodeAt(i)); } return mistmatch; 

As for Node.js


Node.js itself was designed as a scalable and asynchronous framework. When any part of the Node.js code wants to make a call to a blocking action (for example, opening a file or writing to a network socket), it registers a callback function (callback), starts the corresponding action, and then exits. Callback itself is invoked using a mechanism called the event loop.

The event loop is what allows Node.js to perform non-blocking I / O operations (even though JavaScript is single-threaded) by uploading operations to the system kernel (when possible). Since most modern kernels are multi-threaded, they can handle several operations performed in the background. When one of these operations completes, the kernel tells Node.js that the corresponding callback function can be added to the polling queue to eventually be executed.

The model built on event management is in itself very super-scalable, since the threads (threads) represented in it are never in a waiting state. But at the same time, this is stated as a problem, since this or that function has to be taken for execution for a long period of time before it is completed.

Since initially a server based on Node.js runs one thread per core (by default) - any one “long-playing” function can occupy the entire core of the processor, causing other functions to idle in standby mode. It is for the above reason that in the browser we can sometimes see a message with an error - “script taking too long to run”. On the server, at this time, “simple” incoming requests occur.

Parse the example


Suppose we have a small service example.com that provides us with a couple of endpoints:

example.com/info

example.com/check

The source code of the service, which you can use for local launch, you can download by reference .

Request for endpoint / info will display us information about our ip-address and the number of our requests.

 "you are 172.25.20.157 request count on your IOLoop: 1" 

A request for / check in the appropriate form will show us whether we could find the right combination of characters or not.

 curl "http://example.com/check?val0=1&val1=1&val2=1&val3=1&val4=1" "you are 172.25.20.157 - At least one value is wrong!" 

What do we know in advance about the system that we will “attack”?

  1. To access the system, we need to provide the correct sequence of characters: val0 = 1 & val1 = 1 & val2 = 1 & val3 = 1 & val4 = 1
  2. The sequence of numbers is presumably in the range from 1 to 100.
  3. The execution time of the algorithm most likely varies from the input data it takes.
  4. The server constantly waits at least 3 seconds before giving a response to prevent a timing attack.

What we do not know is that the correct login data looks like this:

Val0 = 4 & val1 = 12 & val2 = 77 & val3 = 98 & val4 = 35

So, since we know that a server spends about 3 seconds processing a single request, it can take thousands of years to select a combination of 100 ^ 5 using a trivial search. The event cycle counter increases in any case, but we can get information about the state of the counter only by calling endpoint / info .

 $ curl "http://example.com/info" you are 172.25.50.175 request count on your IOLoop: 1 $ curl "http://example.com/info" you are 172.25.50.175 request count on your IOLoop: 2 $ curl "http://example.com/check?val0=1&val1=1&val2=1&val3=1&val4=1" you are 172.25.20.157 - At least one value is wrong! $ curl "http://example.com/info" you are 172.25.50.175 request count on your IOLoop: 4 

Sending requests for / check with different combinations of numbers will not give us any significant differences in execution time. The algorithm seems to take less than 3 seconds to complete. We also noticed that sending a request to / info while it is processing a request to / check provides us with a clue to which we have devoted this study. Here we just come to the aid of the concept of a cycle of events, which we described above.

While the endpoint call is being processed / check , the event loop does not take control, forcing the endpoint / info call to hang. In contrast, the setTimeout function simply logs the scheduled event and gives control, allowing requests to the / info controller to be processed very quickly. How do we apply timing now? Very simple. We will send a request for / check and, without waiting for an answer, we will immediately send a request for / info , measuring the response time.

 $ curl "http://example.com/check?val0=1&val1=1&val2=1&val3=1&val4=1" you are 172.25.20.157 - At least one value is wrong! $ curl "http://example.com/info" you are 172.25.50.175 request count on your IOLoop: 2 

In fact, during such requests the following happens:



As we can see, measurement will show us that when setting val0 = 4 , the system will respond to us with a slight delay. Thus, based on such delays, we can eventually recover all the necessary code sequence.

Ways of protection:


Conclusion


Timing attacks today can be a real threat for both small applications and large ones. Practice shows that even from small differences in the execution time of queries to the system, you can gather enough information that can be used by attackers. In particular, from the point of view of Node.js, a cycle of events can be used.

Links

When writing a post, I used this material.
Plus some Wikipedia content.

Source: https://habr.com/ru/post/338170/


All Articles