85

血淋淋的事实告诉你:你为什么不应该在JS文件中保存敏感信息

 5 years ago
source link: http://4hou.win/wordpress/?p=24725&%3Butm_source=tuicool&%3Butm_medium=referral
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.

在JavaScript文件中存储敏感数据,不仅是一种错误的实践方式,而且还是一种非常危险的行为,长期以来大家都知道这一点。

reERFfA.jpg!web

而原因也非常简单,我们可以假设你为你的用户动态生成了一个包含API密钥的JavaScript文件:

apiCall= function(type, api_key, data) { ... }
var api_key = '1391f6bd2f6fe8dcafb847e0615e5b29'
var profileInfo = apiCall('getProfile', api_key, 'all')

跟上述例子一样,每当你在全局范围创建一个变量,意味着网站中任何一部分代码都可以访问到这个变量,包括你托管的其他脚本在内。

为什么这样做很明显是不安全的?

为什么开发人员不应该在JavaScript文件中嵌入敏感信息呢?原因有很多,对于经验不丰富的开发人员来说,通过JavaScript文件来传递数据是一种非常简单的方法,因为你可以将数据在服务器端生成和存储,然后将它们传递给客户端代码,而且这样还可以节省一部分发送给服务器端的请求。但是,这种时候我们通常会忽略的一个因素就是浏览器的扩展插件,有的时候为了使用相同的窗口对象,它们有的时候需要直接在DOM中注入script标签,因为仅仅依靠内容脚本可能无法实现预期的功能。

有没有办法保护变量的安全?

我们之前已经讨论了全局范围了,对于浏览器中的JavaScript来说,一个全局变量对于窗口对象来说是非常有用的。但是在ECMA Script5中,还有一种额外的范围,也就是函数范围。这也就意味着,如果我们使用var关键字在一个函数内部声明了一个变量,它就不是全局变量了。而在ECMA Script 6中又引入了另一种范围,即块范围,这个范围内的变量使用const和let关键字来声明。

这两种关键字可以用来声明块范围中的变量,但是我们无法修改const变量的值、如果我们没有用这些关键词来声明变量,或者说我们在函数外部使用了var变量,我们就相当于创建了一个全局变量,而这种情况并不是我们经常想要出现的。

“use strict”

为了防止你不小心创建了全局变量,其中一种有效方法就是激活限制模式,大家可以在一个文件或函数的起始位置添加字符串“use strict”来实现这个功能。接下来,如果你之前没有声明这个变量的话,你将无法使用这个变量。

"use strict";
var test1 = 'arka' // 有效
test2= 'kapı' // 引用错误

我们可以在IIFE(立即调用的函数表达式)中使用这种技术,IIFE可以用来创建一个函数范围,但是它们会立即执行函数主体,比如说:

(function(){
    "use strict";
    //在函数范围内声明变量
    var privateVar = 'Secret value';
})()
console.log(privateVar) // 引用错误

可能乍看过去这会是一种创建变量的有效方式(其内容无法在范围外读取),但其实不然。虽然IIFE是一种防止全局命名空间被干扰的有效方式,但是它们并不能真正保护你的数据内容。

从私有变量中读取敏感数据

实际上,我们几乎无法保证私有变量中的内容真正的是“私有”的。原因有非常多,我们接下来会对其中的部分进行测试,虽然不够完整,但也足够证明给大家看,为什么我们不能在JavaScript文件中存储敏感数据。

重写原生函数

在下面的例子中,我们将使用一个api密钥来向服务器端发送请求。因此,我们需要通过网络并以明文数据的形式发送这个密钥,而且现在在JavaScript中也没有多少其他可选择的方法。假设我们的代码使用了fetch()函数:

window.fetch= (url, options) => {
    console.log(`URL: ${url}, data:${options.body}`);
};
//EXTERNAL SCRIPT START
(function(){
    "use strict";
    var api_key ="1391f6bd2f6fe8dcafb847e0615e5b29"
    fetch('/api/v1/getusers', {
        method: "POST",
        body: "api_key=" + api_key
    });
})()
//EXTERNAL SCRIPT END

你可以看到,我们可以直接重写fetch()函数,然后窃取API密钥。唯一的前提就是我们需要在我们的脚本块后include一个外部脚本。在这个例子中,我们只是在控制台console.log出来了这个API密钥,但实际操作中我们还需要将其发送到我们的服务器中。

定义Setter和Getter

私有变量中可能不仅包含字符串,还有可能包含对象或数组。对象可以有不同的属性,在多数情况下,你可以直接设置和读取它们的值,但是JavaScript还支持很多其他有意思的功能。比如说,你可以在一个对象的属性被设置或被访问的时候执行另一个函数,这里可以使用__defineSetter__和__defineGetter__函数来实现。如果我们在对象构造函数的原型中使用__defineSetter__函数,我们就可以输出分配给目标对象属性的所有值。

Object.prototype.__defineSetter__('api_key',function(value){
    console.log(value);
    return this._api_key = value;
});
Object.prototype.__defineGetter__('api_key',function(){
    return this._api_key;
});
//EXTERNAL SCRIPT START
(function(){
    "use strict"
    let options = {}
    options.api_key ="1391f6bd2f6fe8dcafb847e0615e5b29"
    options.name = "Alice"
    options.endpoint ="get_user_data"
    anotherAPICall(options);
})()
//EXTERNAL SCRIPT END

如果分配给对象属性的是一个API密钥,那我们就可以直接在setter中访问它了。另一方面,getter也可以确保我们的后续代码能够正确执行。

自定义枚举器

数组肯定是不能忽略的一个因素,如果代码中使用了for循环来遍历数组值,我们就可以在数组构造器原型中定义一个自定义的枚举器,这样不仅可以允许我们访问数组中的内容,而且也不会影响原生函数的功能。

Array.prototype[Symbol.iterator]= function() {
    let arr = this;
    let index = 0;
    console.log(arr)
    return {
        next: function() {
            return {
                value: arr[index++],
                done: index > arr.length
            }
        }
    }
};
//EXTERNAL SCRIPT START
(function(){
    let secretArray = ["this","contains", "an", "API", "key"];
    for (let element of secretArray) {
        doSomething(element);
    }
})()
//EXTERNAL SCRIPT END

后话

除了本文所介绍的方法之外,攻击者还有很多从JavaScript文件中窃取敏感信息的方法。有的情况下,使用IIFE、限制模式和在函数/块范围声明变量都不一定能保证你的安全。因此,我建议大家可以从服务器端动态获取敏感数据,而不是直接在JavaScript文件中存储敏感数据。这样不仅更加安全,而且还易于维护,何乐而不为?

* 参考来源: arkakapimag ,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK