如何提升JavaScript安全性,保护应用程序免受威胁
JavaScript作为Web开发的主要开发语言,在前端应用开发中发挥着绝对主导的作用,保护我们的应用免受常见的安全威胁是每个前端开发人员应该掌握的基础知识。本文介绍了JavaScript基础的安全实践,了解如何防止XSS、CSRF等常见漏洞,实现安全通信并安全的管理依赖项、保护客户端数据存储,提升应用应对常见威胁的防御能力。
跨站点脚本(XSS Cross-Site Scripting)攻击
人们经常将跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为XSS。
跨站点脚本是Web应用程序中最常见的漏洞之一,攻击者可以使用户在浏览器中执行其预定义的恶意脚本,达到其攻击目的,如劫持用户会话,插入恶意内容、重定向用户、使用恶意软件劫持用户浏览器、繁殖XSS蠕虫,甚至破坏网站、修改路由器配置信息等。
跨站点脚本攻击XSS的类型
反射型(非持久型)XSS
反射型XSS,是指攻击者在页面中插入恶意JavaScript脚本,当合法用户正常请求页面时,该恶意脚本会随着Web页面请求一并提交给服务器,服务器处理后进行响应,响应由浏览器解析后将JavaScript脚本的执行结果显示在页面中。整个过程就像是一次“客户端—服务器—客户端”的反射过程,恶意脚本没有经过服务器的过滤或处理,就被反射回客户端直接执行并显示相应的结果。反射型XSS需要攻击者将带有恶意脚本的链接发送给其他用户,并诱惑用户点击该链接后才会发生。反射型XSS只有在用户点击时才会触发,且只执行一次,恶意代码不会在服务器中存储,因此也被称为“非持久型XSS”。
存储型(持久型)XSS
存储型XSS又称持久型XSS,攻击脚本将被永久地存放在目标服务器的数据库或文件中,具有很高的隐蔽性。反射型XSS的被攻击对象一般是攻击者去寻找的,就比如说:一个攻击者想盗取A的账号,那么攻击者就可以将一个含有反射型XSS的特制URL链接发送给A,然后用花言巧语诱骗A点击链接。当A不小心点进去时,就会立即受到XSS攻击。这种攻击方式需要一点骗术,所以这种攻击范围不是特别的广,并且提交漏洞时要么平台不认,要么会被认定为低危漏洞。
存储型XSS可以采用广撒网的方式,就是攻击者将存储型XSS代码写进一些有XSS漏洞的网站上,只要有用户访问这个链接就会自动中招。所以我们可以看出,存储型XSS的危害性更大,范围更广,可以不需要寻找被攻击对象,只要存储型XSS在服务器上就能实施攻击。所以提交的存储型XSS评级一般为中危漏洞。
基于DOM的XSS
在网站页面中有许多元素,当页面到达浏览器时,浏览器会为页面创建一个顶级的Document对象,并生成各个子文档对象。每个页面元素对应一个文档对象,这些对象包含属性、方法和事件。客户端的脚本程序(如JavaScript)可以通过DOM动态修改页面内容,从客户端获取DOM中的数据并在本地执行。
攻击者通过构造特殊的URL或利用网页中的某些功能(如表单提交、URL参数等),将包含恶意代码的请求发送给受害者。当受害者的浏览器处理这些请求时,恶意代码会修改DOM树,并执行其中的脚本,从而达到攻击目的。
预防技术
输入验证和消毒
在应用程序中使用用户输入之前,请务必对其进行验证和清理。
// 错误做法: 直接将用户输入的内容放入DOM
document.getElementById('username').innerHTML = userInput;
// 正确做法: 插入前对输入内容进行“消毒”
import DOMPurify from 'dompurify';
document.getElementById('username').textContent = DOMPurify.sanitize(userInput);
内容安全策略(CSP)
实施更健壮的内容安全策略来限制应用程序可加载的内容来源。
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' https://trusted-cdn.com;">
访问cookie时使用HttpOnly和安全标志
防止客户端访问敏感的 cookie。
// 创建cookies时设置HttpOnly和安全标志
document.cookie = "session=123; HttpOnly; Secure";
跨站请求伪造(CSRF)
CSRF (Cross-site request forgery,跨站请求伪造)也被称为One Click Attack或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装成受信任用户请求受信任的网站。
简单的说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己以前认证过的站点并运行一些操作(如发邮件,发消息,甚至财产操作(如转账和购买商品))。因为浏览器之前认证过,所以被访问的站点会觉得这是真正的用户操作而去运行。
预防技术
只使用JSON API
使用JavaScript发起AJAX请求是限制跨域的,并不能通过简单的 表单来发送JSON,所以,通过只接收JSON可以很大可能避免CSRF攻击。
验证HTTP Referer字段
根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,比如用户想要在网站WebA中进行转账操作,那么用户必须先登录WabA,然后再通过点击页面上的按钮出发转账事件。这时该转帐请求的 Referer 值就会是转账按钮所在的页面的URL,如果黑客要对银行网站实施 CSRF攻击,他只能在他自己的网站构造请求,当用户User通过黑客的网站发送请求到WebA时,该请求的 Referer 是指向黑客自己的网站。
因此,要防御 CSRF 攻击,网站WebA只需要对于每一个转账请求验证其 Referer 值,如果是以网站WebA的网址开头的域名,则说明该请求是来自WebA自己的请求,是合法的。如果 Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。
使用 CSRF令牌
为每个用户会话生成并验证 CSRF 令牌。
// 服务端: 生成并分发CSRF令牌
const csrfToken = generateCSRFToken();
res.cookie('XSRF-TOKEN', csrfToken, { httpOnly: true });
// 客户端:在请求中包含令牌
fetch('/api/data', {
method: 'POST',
headers: {
'X-XSRF-TOKEN': getCookie('XSRF-TOKEN')
},
// ... 其他选项
});
SameSite Cookie属性
使用 SameSite 属性可以防止在跨站点请求中发送 cookie。
// 创cookies时设置SameSite属性
document.cookie = "session=123; SameSite=Strict";
注入攻击
虽然SQL注入众所周知,但JavaScript代码注入攻击也是一种常见的网络攻击方式,攻击者可以通过在网页中注入恶意JavaScript代码,对用户进行攻击。例如,在JavaScript中,prompt()函数用于弹出一个对话框,提示用户输入信息,如果攻击者利用prompt()函数进行代码注入攻击,可能会导致用户的敏感信息被窃取。
预防技术
避免使用 eval() 和 new Function()
切勿将eval()或new Function()与用户提供的输入一起使用。
// 不好的用法: 对用户的输入进行eval
eval('console.log("' + userInput + '")');
// 好的用法: 避免与用户输入一起使用eval
console.log(userInput);
使用参数化查询
使用数据库时,始终使用参数化查询或准备好的语句。
// 在进行数据库操作时,使用参数化的查询,不要使用用户输入内容或提交的参数拼接sql语句
const query = {
text: 'INSERT INTO users(name, email) VALUES($1, $2)',
values: [userName, userEmail],
}
client.query(query)
使用https等措施确保通信安全
确保客户端和服务器之间的安全通信是应用程序安全的基础
实施技术
强制使用 HTTPS
将所有 HTTP 流量重定向到 HTTPS 并使用 HSTS(HTTP 严格传输安全)。
// Express.js 中间件将HTTP重定向到HTTPS
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`)
} else {
next()
}
})
实施适当的CORS(跨资源共享)策略
正确配置 CORS(跨源资源共享),以防止未经授权的API访问。
// Express.js CORS 配置
const cors = require('cors');
app.use(cors({
origin: 'https://trusted-origin.com',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
注意第三方依赖,保证供应链安全
使用第三方库可能会给应用程序带来漏洞。
安全实践
定期依赖审计
定期审核并更新应用的依赖项。
# 使用npm来审计依赖项
npm audit
# 尽可能及时的修复漏洞
npm audit fix
使用子资源完整性
从 CDN 加载脚本时,使用子资源完整性来确保内容没有被篡改。
<script src="https://example.com/example-framework.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
保护本地数据
在客户端存储数据时,应采取预防措施保护敏感信息。
最佳实践
加密敏感数据
在 localStorage 或 IndexedDB 中存储敏感数据时使用加密。
// 使用Web Crypto API进行加密
async function encryptData(data, key) {
const encodedData = new TextEncoder().encode(data);
const encryptedData = await window.crypto.subtle.encrypt(
{ name: "AES-GCM", iv: window.crypto.getRandomValues(new Uint8Array(12)) },
key,
encodedData
);
return encryptedData;
}
使用 SessionStorage 存储临时数据
对于仅在会话期间需要的敏感数据,应使用 sessionStorage 而不是 localStorage。
// 存储临时数据
sessionStorage.setItem('tempAuthToken', token);
// 获取临时数据
const tempToken = sessionStorage.getItem('tempAuthToken');
防范安全威胁
保护JavaScript应用程序安全是一个持续的过程,需要保持警惕并不断学习。随着新威胁的出现,开发人员必须随时了解情况并相应地调整其安全实践。
请记住,安全性不是一项功能,而是高质量软件开发的一个基本方面。通过实施这些安全最佳实践并不断学习有关新兴威胁的知识,可以显著降低应用程序中出现安全漏洞的风险。