4

Suppose you have the following code in Node:

const { token } = req.body
const hash = crypto.createHmac('sha256', SECRET).update(token).digest('hex')
const user = await User.findById(req.session.userId)

if (hash === user.rememberMeHash) {/*...*/}

The string comparison above is deemed vulnerable to a timing attack because it can leak the character position on a mismatch, so the correct way is

// Hashes are already equal in length because the same hash function was used
if (crypto.timingSafeEqual(new Buffer(hash), new Buffer(user.rememberMeHash))

While true in principle, I can't see how this leak is practically possible. To get reliable time measurements, you'd need to

  • isolate the code snippet to avoid interference from side effects (request handling, Express routing, DB queries);
  • run a large number of empirical tests in a strictly identical environment (same CPU & memory usage, processes, OS);
  • have access to a local server instance that has no traffic or intervention from outside.

None of these are realistic in a distributed system, much less to an attacker with no privileged access and no knowledge of the specific hashing algorithms and secret keys employed.

In practice, you will necessarily get varying and inconsistent results when timing any code, particularly one that is just-in-time compiled like JavaScript. This is well understood in algorithm analysis which doesn't directly measure algorithm runtime because these measurements are acutely sensitive to the underlying hardware, software, compiler, language, etc. In this particular case, compared to a database query or a network call (or even script processing when running node binary on a .js file), string comparison takes a minuscule amount of CPU time to process.

Now, also consider that the above code runs across a cluster of servers behind a load balancer. As such, HTTP response times will vary depending on other incoming and ongoing requests (i.e. website traffic), background processes, hosting provider uptime, network fluctuations (e.g. speed drops), use of Tor or a VPN, and hundreds of other factors.

Considering a real-world web server architecture, how can a mere string comparison ever be exploited in a timing attack?

Alex
  • 141
  • 2
  • I assume you mean over the internet rather than just a web server, as local networks can have low enough latency that it may be possible, You can average out the time by sending the same request several times. Over the internet there are enough factors to make it difficult to do. Also different deployments affect vulnerabilities, you need to consider contextual risk, so you should only consider the deployment model that applies to you. – wireghoul Dec 15 '19 at 03:15
  • I should also add that as it generally takes less time to resolve this issue than arguing against fixing it you should just fix it. – wireghoul Dec 15 '19 at 05:19
  • 2
    Does this answer your question? [Are string comparson timing attacks practical over public networks](https://security.stackexchange.com/questions/88954/are-string-comparson-timing-attacks-practical-over-public-networks) – Peteris Dec 15 '19 at 13:55
  • 1
    While string comparison takes a tiny amount of CPU time to process, tiny variations in speed (~100 nanoseconds?) can be measured over a network. A nontrivial number of requests *is* required, but you don't need to isolate the code snippet nor have an access to a local server nor need it to be completely isolated from other requests. – Peteris Dec 15 '19 at 13:58
  • @wireghoul Not arguing at all, just curious why this isn't considered a premature optimization. To me, in the context of JS, it's like caching an array.length in a for loop. The difference is so minute, it won't be reflected with `console.time()`, and whatever time fluctuations you do register will be due to unrelated factors like other running processes. In a C binary with specialized tools to examine assembly code perhaps, but in JS, no way. – Alex Dec 15 '19 at 14:35
  • @Peteris Thanks for sharing, although I would've loved a more detailed answer. On a distributed system across the internet, response times will vary by milliseconds, not nanoseconds. Requests are also throttled, more aggressively on auth endpoints. The linked paper talks about LAN attacks, not the web; that's why I suggested, to get _any_ sensible approximations you'd need an ideal test environment with no interference, which is unfeasible on a live production system. – Alex Dec 15 '19 at 14:36
  • @Alex it's often possible to turn a remote attack into a LAN attack - for example, if it's distributed on a major cloud provider, you can get an attacking machine in the same datacenter as the target host with very low latencies between you and the target. – Peteris Dec 15 '19 at 14:49
  • 2
    @Alex it isn't premature optimization because security != optimization. While it may not be easy to exploit it over the internet, attacks rarely occur as a singularity and it may be exploitable through SSRF or from another compromised server in the DMZ. Or perhaps your website is migrated to a single more powerful server in the future? As I said to understand the risk it poses to *your* system you would need to consider it in the context of *your* system. – wireghoul Dec 15 '19 at 15:19
  • 2
    See also [Remote Timing Attacks are Practical](https://crypto.stanford.edu/~dabo/papers/ssl-timing.pdf) (PDF). – Sjoerd Dec 16 '19 at 11:29
  • See also [Nanosecond Scale Remote Timing Attacks On PHP Applications: Time To Take Them Seriously?](https://web.archive.org/web/20160126070339/blog.astrumfutura.com/2010/10/nanosecond-scale-remote-timing-attacks-on-php-applications-time-to-take-them-seriously/) (Archived) from almost a decade ago. I think that conditions favoring such attacks have mostly only increased in the intervening time. – CrunchBangDev Dec 17 '19 at 15:14

1 Answers1

3

Timing attacks are possible even if response times seem inconsistent, because you can use statistical analysis to filter out a lot of noise in the data. Average values are much more stable than instantaneous values, for example. A fixed delay (like the one introduced by the comparison) will be added to the average, while inconsistent delays (like the ones introduced by the network) will cancel out. Example:

Response times: 17, 20, 1, 15, 62, 35   
Average: 25

Response times (+1 delay): 18, 21, 2, 16, 63, 36
Average: 26

So as you can see the fixed delay is detectable in the average value, and instantaneous spikes don't matter. Finding a stable average value might be difficult though, and it might require a lot of attempts (thousands or even millions of requests). Also the average value might not even be stable enough, and other statistical analysis might be taken into account.

This complexity makes remote timing-attacks impractical in many situations, although research has shown that they are possible within local networks. This means that an attacker might well try to use such timing-attacks once they have broken into your local network, for example. On the other hand, it might be impossible to use the same attack from a remote location, or it might not be worth it at all anyway, depending on several factors like the configuration of the network and your server. A remote attacker will therefore focus on other vulnerabilities to exploit. Also remember that you can avoid this problem by implementing some kind of rate-limiting in your server, banning any potential attacker after too many attempts.

reed
  • 15,398
  • 6
  • 43
  • 64
  • I think this is a good answer. The only critique I'd have is that the scale of the additional time matters. The smaller the difference in time, relevant to noise, the larger the amount of data you'll need to compare. If the time difference in comparison is 1 nanosecond, and your noise is in milliseconds, how much data do you need to find the signal through the noise? – Steve Sether Dec 17 '19 at 17:09