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

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

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

[[245344]]

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

  1. apiCallfunction(type, api_key, data) { ... }  
  2. var api_key = '1391f6bd2f6fe8dcafb847e0615e5b29' 
  3. 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”来实现这个功能。接下来,如果你之前没有声明这个变量的话,你将无法使用这个变量。

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

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

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

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

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

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

1. 重写原生函数

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

  1. window.fetch= (url, options) => {  
  2.     console.log(`URL: ${url}, data:${options.body}`); 
  3. };  
  4. //EXTERNAL SCRIPT START  
  5. (function(){  
  6.     "use strict";  
  7.     var api_key ="1391f6bd2f6fe8dcafb847e0615e5b29"  
  8.     fetch('/api/v1/getusers', {  
  9.         method: "POST",  
  10.         body: "api_key=" + api_key  
  11.     });  
  12. })()  
  13. //EXTERNAL SCRIPT END 

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

2. 定义Setter和Getter

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

  1. Object.prototype.__defineSetter__('api_key',function(value){  
  2.     console.log(value);  
  3.     return this._api_key = value 
  4. });  
  5. Object.prototype.__defineGetter__('api_key',function(){  
  6.     return this._api_key;  
  7. });  
  8. //EXTERNAL SCRIPT START  
  9. (function(){  
  10.     "use strict"  
  11.     let options = {}  
  12.     options.api_key ="1391f6bd2f6fe8dcafb847e0615e5b29"  
  13.     options.name = "Alice"  
  14.     options.endpoint ="get_user_data"  
  15.     anotherAPICall(options);  
  16. })()  
  17. //EXTERNAL SCRIPT END 

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

3. 自定义枚举器

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

  1. Array.prototype[Symbol.iterator]= function() {  
  2.     let arr = this 
  3.     let index = 0 
  4.     console.log(arr)  
  5.     return {  
  6.         next: function() {  
  7.             return {  
  8.                 value: arr[index++],  
  9.                 done: index > arr.length  
  10.             }  
  11.         }  
  12.     }  
  13. };  
  14. //EXTERNAL SCRIPT START  
  15. (function(){  
  16.     let secretArray = ["this","contains", "an", "API", "key"];  
  17.     for (let element of secretArray) {  
  18.         doSomething(element);  
  19.     }  
  20. })()  
  21. //EXTERNAL SCRIPT END 

四、后话

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

责任编辑:赵宁宁 来源: FreeBuf
相关推荐

2018-11-12 10:14:56

网络安全网络安全技术周刊

2021-04-16 09:31:07

Rosetta 2安全软件攻击

2020-09-18 07:01:38

分页offsetlimit

2023-04-04 08:10:40

CSS字体元素

2020-06-05 14:09:42

Kubernetes容器应用程序

2021-01-08 10:48:48

码农编程编码测试

2023-08-01 08:18:09

CSSUnset

2018-09-28 16:17:20

Java 11升级Oracle

2020-03-09 13:58:19

机器人机器人流程自动化RPA

2012-06-05 10:09:57

2018-11-20 14:03:17

数据科学数据分析数据科学家

2012-05-07 17:06:29

2022-12-16 09:47:29

2015-09-28 08:57:06

Ruby APPNeo4j

2019-04-04 14:33:19

云计算云端企业

2022-03-09 08:14:24

CSS容器container

2018-02-06 08:49:07

LinuxUnix命名管道

2020-06-21 21:25:14

物联网WiFiIOT

2013-07-15 10:32:32

Windows虚拟机红帽

2019-09-02 09:30:40

点赞
收藏

51CTO技术栈公众号