浅析node应用的timing-attack安全漏洞
什么是timing-attack安全漏洞
timing-attack安全漏洞是指黑客能够通过研究特定的计时规律来发现安全漏洞或者密码,从而实现非法访问或者窃取敏感信息的目的。该攻击方法主要利用计算机在运行指令时执行速度的差异来实现,通过对两个不同指令的响应时间进行比较,来推算出信息。
在node应用中,通过比较密码验证时间,就有可能被黑客利用来判断密码是否正确,从而攻击系统。
如何避免timing-attack安全漏洞
为了避免timing-attack安全漏洞,我们需要在代码中采取以下几种措施:
- 使用时间常量比较函数,禁止使用字符串比较函数。
// 代码示例1
const crypto = require('crypto');
// 时间常量比较函数,比较时间固定
function safeEquals(a, b) {
if (a.length !== b.length) {
return false;
}
let result = 0;
for (let i = 0; i < a.length; i++) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return result === 0;
}
function verifyPassword(password, hash, callback) {
const args = hash.split('$');
const iterations = parseInt(args[1], 10);
const salt = args[2];
const storedHash = args[3];
crypto.pbkdf2(password, salt, iterations, 32, 'sha256', (err, derivedKey) => {
if (err) {
return callback(err);
}
const calculatedHash = derivedKey.toString('hex');
callback(null, safeEquals(calculatedHash, storedHash));
});
}
- 将密码常量化,通过时间比较函数比较密码的散列值。
// 代码示例2
const crypto = require('crypto');
function verifyPassword(password, hash, callback) {
const [iterations, salt, storedHash] = hash.split('$');
crypto.pbkdf2(password, salt, parseInt(iterations, 10), 32, 'sha256', (err, derivedKey) => {
if (err) {
return callback(err);
}
const calculatedHash = derivedKey.toString('hex');
let result = true;
for (let i = 0; i < calculatedHash.length; i++) {
result &= (calculatedHash[i] === storedHash[i]);
// 增加适当的延时来避免计算时间被利用
if (!result) {
setTimeout(() => {
callback(null, false);
}, 500 + Math.floor(Math.random() * 1500));
break;
}
}
setTimeout(() => {
callback(null, result);
}, 500 + Math.floor(Math.random() * 1500));
});
}
示例说明
下面我们通过两个示例来说明timing-attack漏洞和避免方法的具体过程。
示例1:使用字符串比较函数
假设在node应用的密码验证中,使用了字符串比较函数:
function verifyPassword(password, hash, callback) {
const calculatedHash = crypto.createHash('sha256').update(password).digest('hex');
callback(null, calculatedHash === hash);
}
黑客通过比较不同密码验证的时间,来判断密码是否正确:
// 使用长度为 10 的字符串作为测试,其中 'a' 代表正确的密码,'b' 到 'z' 代表错误密码
const hash = crypto.createHash('sha256').update('a').digest('hex');
for (let i = 1; i < 30; i++) {
const password = 'a' + 'b'.repeat(i);
const startTime = Date.now(); // 记录开始时间
verifyPassword(password, hash, (err, result) => {
if (err) {
console.error(err);
} else {
const endTime = Date.now(); // 记录结束时间
console.log(`${password}: ${result}, ${endTime - startTime}ms`);
}
});
}
黑客通过比较不同密码的验证时间,可以得到正确密码的长度和位置信息:
// 输出结果
abbbbbbbbbb: false, 10ms
accccccccc: false, 10ms
adddddddd: false, 10ms
aeeeeeeee: false, 11ms
afffffffff: false, 11ms
...
a: true, 12ms
ab: true, 12ms
aba: true, 13ms
abaa: true, 13ms
abaaa: true, 13ms
...
在这个例子中,黑客可以通过计时推算出正确密码的长度和位置信息,从而实现非法访问系统。
示例2:使用时间常量比较函数
为了避免timing-attack漏洞,我们采用时间常量比较函数的方法重写密码验证函数:
function verifyPassword(password, hash, callback) {
const [salt, storedHash] = hash.split('$');
crypto.pbkdf2(password, salt, 10000, 32, 'sha256', (err, derivedKey) => {
if (err) {
return callback(err);
}
const calculatedHash = derivedKey.toString('hex');
let result = 0;
for (let i = 0; i < calculatedHash.length; i++) {
result |= calculatedHash.charCodeAt(i) ^ storedHash.charCodeAt(i);
}
callback(null, result === 0);
});
}
在这个方法中,我们用时间常量比较函数safeEquals,检查计算得到的散列值和存储散列值是否相同,以避免timing-attack攻击。
function safeEquals(a, b) {
if (a.length !== b.length) {
return false;
}
let result = 0;
for (let i = 0; i < a.length; i++) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return result === 0;
}
这个方法中使用了时间常量比较函数,将每个字符串的比较时间固定为常量时间,从而避免了timing-attack攻击。在函数内部,我们还增加了一个随机延时,来防止黑客通过实时计算换来误导。
setTimeout(() => {
callback(null, result);
}, 500 + Math.floor(Math.random() * 1500));
在这个例子中,黑客无法通过计时推算出正确密码的长度和位置信息,从而无法实现非法访问系统。
总结
在node应用中,采用时间常量比较函数比较密码散列值的方式可以有效避免timing-attack漏洞。此外,增加随机延时等一些措施也可以可降低其被攻击的概率。但若密码过于简单,则仍有可能被黑客计算出,因此我们建议采用更加安全的密码策略来提高系统安全性。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:浅析node应用的timing-attack安全漏洞 - Python技术站