JavaScript安全编码十大实用技巧

本文详细介绍了JavaScript安全编码的十大关键技巧,涵盖跨站脚本防护、框架安全使用、输入验证、安全函数选择等内容,帮助开发者构建更安全的Web应用程序。

JavaScript安全编码

JavaScript是整个互联网的前端。无论你是将TypeScript转译为JavaScript,创建快速的node.js脚本,还是构建调用更有趣API集合的漂亮但简单的前端,它几乎无处不在。由于JavaScript如此普及,它成为攻击者的主要目标。在本文中,我们将介绍编写更安全JavaScript的十个技巧。

1. 跨站脚本

谈到JavaScript安全时,首要讨论的总是跨站脚本(XSS)。跨站脚本是一种注入形式;意味着攻击者混淆了你的应用程序,使其解释或执行他们的恶意代码,而不是将其视为数据。用户输入应始终被视为数据,但不幸的是,如果我们不小心,计算机可能会被欺骗。

XSS是唯一仅在JavaScript中有效的注入类型。它也是唯一直接攻击用户的注入类型,通过控制浏览器并利用它来对抗受害者。所有其他类型的注入不攻击用户;例如,SQL注入攻击数据库服务器,命令注入攻击系统运行的主机操作系统,LDAP注入攻击LDAP服务器。你应该明白了。

通过XSS,攻击者可以使用浏览器访问你的cookie(包括会话信息,如果你以不安全的方式将其存储在cookie中)、外部脚本(如果你没有使用内容安全策略锁定)、安装键盘记录器、破坏你的网站等。JavaScript能够做的任何事情,XSS攻击也可以做到;唯一的限制是攻击者的想象力。

尽管多年来由于各种形式的意识和教育(如OWASP十大Web应用风险)、新的JavaScript框架自动执行输出编码以及团队比以往任何时候都更重视安全,XSS的实例有所减少,但不幸的是,它仍然是一个高风险问题。

要消除XSS影响应用程序的机会,请执行以下操作:

  • 对所有用户提供或用户可修改的数据执行输入验证。执行输入验证后,如果你必须接受潜在危险字符(如<、>、’、"、-等),你应该通过转义它们(在前面添加反斜杠)或清理它们(字面上用另一个字符替换或完全删除)来保护你的应用程序。
  • 对任何将显示到屏幕上的内容执行输出编码,包括任何可能显示的内容(例如,如果你从API返回你知道前端将显示的内容)。如果你能让框架为你完成这项工作,这是确保你正确执行此操作的最简单且通常最有效的方法。输出编码可能变得相当复杂,特别是如果你正在执行内联JavaScript。没有人想要进行嵌套编码!
  • 使用内容安全策略头(CSP)列出你允许作为应用程序一部分的所有第三方组件,尤其是脚本。恶意XSS攻击要做的第一件事就是尝试调用互联网上另一个更大的恶意脚本。大多数字段只允许50或100个字符,这对于编写攻击代码来说空间不大。如果他们能够调用网络上的恶意站点,然后调用更长的脚本,你的风险将呈指数级增加。
  • 将httpsOnly标志添加到你的cookie,以确保如果你确实遗漏了什么,攻击者将永远无法从你的cookie访问会话信息。尝试访问你的cookie通常是XSS攻击尝试做的第二件事,所以不要让他们访问这些敏感信息。
  • 执行手动代码审查,使用静态(SAST)或动态(DAST)分析工具,或执行渗透测试,以绝对确保你没有遗漏任何东西!

2. 使用为你输出编码的JavaScript框架

React、Angular和Vue.js自动执行输出编码,并且还有许多其他惊人的功能。使用任何框架时,请小心检查是否有你应该避免或小心使用的危险函数,例如React的dangerouslySetInnerHTML和Angular的bypassSecurityTrustAs*函数。

3. 避免内联脚本

如果你"只是想快速做点什么",将JavaScript直接包含在HTML中可能很诱人,但这大大增加了XSS的可能性(并为以后创建了维护问题)。除此之外,这很混乱。将JavaScript保存在单独的外部文件中,以保持额外的安全层和组织性。你可以在CSP头中定义你的脚本,以保持一切整洁有序。这类似于内联SQL,当我们组合用户输入然后直接将其馈送到数据库执行时……危险情况就会发生。

4. 使用严格模式

我们不是尼安德特人,这意味着我们当然想编写漂亮、干净、语法正确的代码。严格模式通过收紧语言规则来帮助我们做到这一点,帮助你编写更安全、更干净和更可预测的代码。你应该在所有提供严格模式的语言中使用它,而不仅仅是JavaScript。

严格模式防止静默错误,不允许不安全操作,阻止你使用保留字(如let)做任何其他事情,防止意外的全局变量,并使this的使用更安全和更可预测。此外,严格模式还应该提高性能,帮助你更早地捕获错误,引导你走向最佳实践,并提高安全性。

5. 使用开源工具

这些风险中的许多是众所周知的,开源社区的开发人员已经创建了减轻这些风险的工具。使用有帮助的、免费和开源软件(FOSS)库和工具来帮助你编写更安全的代码,例如:

  • DomPurify用于针对XSS清理输入。
  • Retire.js用于查找过时、不受支持或其他危险的JavaScript库。
  • Npm Audit、Yarn Audit、Snyk CLI(免费版本)检查你的依赖项是否是最新的。
  • DOM Snitch是一个Google项目,创建了一个实验性的Chrome扩展,使开发人员和测试人员能够识别客户端代码中常见的不安全实践。
  • Nodejsscan一个用于node.js应用程序的免费静态分析工具。
  • Semgrep Community edition、Bearer CLI(免费版本)、Horusec等执行免费静态分析。它们都适用于多种语言,包括JavaScript。
  • Zap执行免费动态分析(DAST)注意:在工作场所运行Zap之前,请获得老板的许可!这是一个黑客工具,未经书面许可对任何网站或应用程序运行它是非法的。如果使用不当,此工具可能造成损害。使用前请阅读相关资料。其他工具使用起来从不危险。

6. 在代码中清晰标识文本

在你的代码中清晰表明文本是文本,并且变量内的任何内容都不是应该执行或解释的代码。我们通过将来自用户的任何内容放入你的JavaScript或CSS的安全数据元素中来确保文本是文本。例如,不要使用innerHtml(它会被渲染);而是使用innerText或textContent,它们显然是仅文本的,用于显示,而不是被解释。始终清晰表明某物是文本;不要让DOM(文档对象模型)决定,因为有时它会出错。

7. 仅将变量应用于安全属性

如果你必须使用变量中的数据设置element.setAttribute(这通常是用户提供的值,因此可能危险),仅使用安全的、静态的属性,而不是动态(可更改)的属性。安全属性的示例包括:align、alink、alt、bgcolor、border、cellpadding、cellspacing、class、color、cols、colspan、coords、dir、face、height、hspace、ismap、lang、marginheight、marginwidth、multiple、nohref、noresize、noshade、nowrap、ref、rel、rev、rows、rowspan、scrolling、shape、span、summary、tabindex、title、usemap、valign、value、vlink、vspace、width)。不要对element.setAttribute使用动态、不安全的属性,例如onclick或onblur。某些事情"发生"或更改的属性不安全,你不应将它们与用户提供的数据(例如保存在变量中的任何数据)一起使用。

当我们讨论使用变量(用户数据)时……它们只应放在CSS属性值中,而不是上下文中。再次强调,我们不希望它们可能使我们的软件以不可预测的方式运行。

示例:<style> selector { property : $varUnsafe; } </style>

8. 在后端验证输入

当出于安全原因执行输入验证(而不是为了可用性、速度或任何其他原因)时,检查必须在后端执行,而不是在前端执行。任何拥有拦截代理(如Zap或Burp Suite)的人都能够"拦截"你前端的请求,对其进行更改,然后将其传递给后端,就好像它没有被干扰一样。如果做得好,后端不知道有人在请求离开前端后是否对其进行了任何更改。这种类型的攻击或测试将在使用应用程序的同一台机器上执行,意味着他们不需要担心加密,他们将直接以明文访问你的整个请求。

为什么这很糟糕?想象一下,你在JavaScript中有输入验证,说你愿意接受a-z、大写和小写,以及所有数字,但没有其他内容。它接受这些数据,然后反射回屏幕供用户阅读。理论上,应用程序将拒绝以下输入"‘ or 1=1 –",因为其中几个字符不在你的允许列表中。然而,想象一下攻击者正在使用拦截代理使用你的应用程序。攻击者在字段中输入"Hello",它通过了输入验证。然后他们拦截你的请求,将字段更改为" ‘ or 1=1 –",然后将其发送到你的应用程序。如果后端没有输入验证,应用程序将以通常的方式使用该数据,在这种情况下可能将其附加到SQL语句,如’select * from table users where username like %’ & your_now_malicious_variable_here & ’ and password = ’ & password_variable。

尽管这是一个简化的示例,但希望你能看到,出于安全原因,输入验证必须在后端执行。你始终也可以在前端执行它,以提高速度和可用性。你只需要确保最终决定权始终在服务器上。

9. 避免有问题的函数

避免以下经常有问题的JavaScript函数。特别是如果你打算将它们与用户提供或用户可修改的数据一起使用。如果你必须使用它们,请极其小心。

eval()、innerHTML、outerHTML、Function()、escape()、unescaped()、document.write()、document.writeln()、unescapeHTML()、decodeURI()和encodeURI()、with()、使用字符串参数(意味着变量)的构造函数、setTimeout()、setInterval()、new function和setAttribute()(回忆:如果你使用setAttribute,仅使用静态值)。

10. 像保护任何其他应用程序一样保护它

执行所有其他安全编码策略,就像你对任何其他应用程序所做的那样,例如执行输入验证/清理/转义、使用参数化查询而不是内联/动态查询构建、加密传输中和静止的数据,以及使用可靠的系统进行身份验证、授权、会话管理、身份和秘密管理等。

在大多数人认为是"安全编码"一部分的标准项目中,确保你的团队遵循安全系统开发生命周期(S-SDLC)是绝对最重要的。当我说安全SDLC时,我的意思是你的团队添加额外的活动和流程,以确保你正在构建和维护的软件是安全、可靠和健壮的。你可以添加到任何方法论的一些活动包括威胁建模、安全代码审查、自动化动态测试、软件组合分析或第三方依赖项的其他验证、软件供应链强化、创建安全用户故事,或将安全需求与所有其他项目需求一起包括。你添加到SDLC的任何安全活动都将有助于确保我们创建更健壮和可信赖的系统,你的用户可以依赖。你添加的越多越好!

编写更安全JavaScript的旅程始于小而一致的改变。选择一两个最佳实践,今天就付诸行动。当你开始看到它们的影响时,花点时间与朋友或同事分享你学到的东西。通过知识共享和渐进式改进,我们可以使JavaScript不仅更美丽和强大,而且对每个人都更安全。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计