Understanding timing attacks with code examples
source link: https://dev.to/propelauth/understanding-timing-attacks-with-code-examples-32e6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Vulnerable login example
The following code snippet has a subtle security issue with it. Can you tell what's wrong?
// Returns true if the email/password pair is valid
async function isValidCredentials(emailAddress, password) {
// Fetch the password hash from the DB by email address
const passwordHashOrNull = await fetchPasswordHash(emailAddress);
// If there was no match, return false
if (!passwordHashOrNull) {
return false;
}
// Bcrypt is "a library to help you hash passwords"
// Here we use the compare function to check that the
// provided password matches the hashed password in the DB
const doesPasswordMatch = await bcrypt.compare(password, passwordHashOrNull);
return doesPasswordMatch;
}
// Fetches the password hash from the DB
async function fetchPasswordHash(emailAddress) {
// impl not important
}
As a hint, let's look at how long a few calls to isValidCredentials
takes:
async function timeIsValidCredentials(emailAddress, password) {
console.time("Checking " + emailAddress);
await isValidCredentials(emailAddress, password);
console.timeEnd("Checking " + emailAddress);
}
await timeIsValidCredentials("[email protected]", "password");
// Checking [email protected]: 63.813ms
await timeIsValidCredentials("[email protected]", "password2");
// Checking [email protected]: 62.867ms
await timeIsValidCredentials("[email protected]", "password");
// Checking [email protected]: 4.017ms
await timeIsValidCredentials("[email protected]", "password");
// Checking [email protected]: 4.008ms
There's a noticeable difference between how long [email protected]
emails take and [email protected]
or [email protected]
.
It turns out that the issue is these lines:
// If there was no match, return false
if (!passwordHashOrNull) {
return false;
}
By returning early if there was no match, an attacker can easily tell that [email protected]
has an account, but [email protected]
and [email protected]
don't.
Timing attacks
This is a common example of a timing attack. They are a class of attacks where the length of time that your application takes to perform a task leaks some information.
In the login case, the difference in times made it pretty obvious from even one request. If the difference was more subtle, an attacker can make many requests over a long time and average them together to distinguish different cases.
Is it a big deal?
This might not seem like a big deal, but let's say I'm trying to find someone's personal email. I only have their name, and I know they have signed up for your site.
I can try a bunch of variations of [email protected]
or lastname{3digitnumber}@gmail.com
and so on until I find a valid one.
Additionally, there are other timing attacks that leak even more sensitive information, which we'll see in a bit.
How can we fix it?
There are a few strategies, but the simplest answer is "make sure all codepaths take the same amount of time". You don't have to do this everywhere, just in sensitive parts of the codebase.
Instead of returning early, we could have checked the password against some hash and then returned false:
// If there was no match, waste time and then return false
if (!passwordHashOrNull) {
await bcrypt.compare(password, RANDOM_PASSWORD_HASH);
return false;
}
It is also useful to add rate limiting whenever possible. If an attacker needs a lot of requests to distinguish different cases, rate limiting them could make the attack impractical.
Timing attacks in practice
Recently, a clever timing attack was found in Lobste.rs' password reset. It exploited the fact that databases when comparing two strings will return early if the strings don't match.
So checking
"a".repeat(10000) === "b".repeat(10000)
should take less time than
"a".repeat(10000) === "a".repeat(9999) + "b"
This means that the more characters you have correct, the longer the call will take. An attacker could try different prefixes and see which one takes the longest to slowly determine a valid password reset token.
This same vulnerability exists anywhere where someone is checking a secret value directly against a database, so while it may seem pretty theoretical, there are definitely real world cases that have been reported and fixed.
Recommend
-
3
计时攻击 Timing Attacks 计时攻击 Timing Attacks 本文来自读者“程序猿石头”的投稿文章《
-
7
Dec 10, 2020 Timing code using computation expressions While building a link checking tool, I wanted to log the time it took to check a link. For demonstration purposes, we’ll define the link checking function as fo...
-
7
New Kids On The Block: Understanding Cold Boot AttacksJanuary 19th 2021 new story
-
7
August 19, 2021
-
10
Reconstruction Attacks: Examples of Invasion of Your Privacy Without ConsentAugust 22nd 2021 new story2
-
5
Weaponizing a NFC reader for basic timing attacks Oct 24, 21 I am currently (since a couple of years, actu...
-
9
Preventing Timing Attacks on String Comparison with a Double HMAC Strategy November 7, 2015 5:49 pm by...
-
5
Timing attacks extract your confidential data from observations of the time used by your computer. Timing attacks have been demonstrated again and again to successfully extract complete cryptographic keys and other secrets. This web site,
-
9
Understanding DNS attacks: Identifying and patching vulnerabilitiesNihad HassanOctober 26, 2022The Domain Name System (DNS) translates domain names into IP addresses. Every device and website has an IP address that o...
-
6
Security and Risk Strategy8 MIN READ
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK