跳转到主要内容

介绍


首先,我要感谢 Elliot Forbes 有机会在这个领域进行合作。我的名字是阿卜杜拉·加西亚。我是一位经验丰富的安全工程师,在广泛的行业领域拥有十多年的成功设计和交付高质量解决方案的经验;对于好奇的人,您可以在 LinkedIn 中找到更多详细信息。我也是一名神经科学家,专注于结合虚拟现实、ML 和假肢的运动神经康复的脑机接口 (BCI)。最后,在业余时间,我喜欢风景/城市/街头摄影(https://abdullahgarcia.myportfolio.com)、旅行、烹饪/烘焙以及各种运动……等等。

本文是几篇文章中的第一篇,其目标是:

  • 了解无论您使用哪种编码语言都适用的通用安全编码最佳实践。
  • 了解使用 Go 编码时如何应对 OWASP 十大(2017)安全风险。

特别是这个:

  • 了解数据清理和最佳实践。
  • 了解输入验证和最佳实践。
  • 在此先感谢您的时间!

通用安全编码最佳实践


在开始本节之前,我想强调以下几点:当你为生活而编码时,我真的认为你有责任在你的理解范围内尽可能地做到最好。希望这种理解将通过自我激励的学习不断增长。这包括拥有嵌入到您的编码实践中的安全注意事项。请记住,无论您编写什么代码,都反映了您的专业精神。此外,您的代码构成了一个应用程序,该应用程序可能会在现实世界中以一种或另一种方式影响/影响人们。以此为荣!

让我们开始!

实践涉及以下领域:

  • 输入验证
  • 输出编码
  • 身份和认证管理
  • 会话管理
  • 访问控制
  • 密码实践
  • 错误处理、日志记录和指标
  • 数据保护
  • 通讯安全
  • 系统配置
  • 数据库安全
  • 文件管理
  • 内存管理
  • 其他

请注意,此列表基于 OWASP SCP 快速参考指南 (v2)。但是,您会找到的内容还包括多年来获得的知识,并突出了一些额外的点。

输入验证


每当讨论输入验证时,数据清理都会与之共存。请注意,有些人也使用术语数据验证;不幸的是,它的用法并不一致,有时相当于输入验证,而其他一些则相当于数据清理。另外,我希望您记住,数据清理的应用因域而异:例如数据擦除、数据处理、数据隐私等。无论如何,我们需要澄清每个术语及其过程,我认为最好的方法是结合上下文和示例。

对,所以,让我们考虑一个有表单的网站,并且在表单中只有一个电子邮件地址的输入字段。为了简单起见,我们假设一个真实的人填写了表单并点击了提交按钮。

在继续之前,我希望您考虑以下问题:

  • 填写表格的人是普通用户还是恶意用户有关系吗?
  • 或者,或者,如果一个脚本填写了表格?

两者的答案都是:不,不是。您应该始终为最坏的情况做好准备:默认情况下,所有输入数据都是不安全的。有趣的是,有一个完整的对话与我之前的陈述有关:已知的已知、已知的未知和未知的未知。我不会在本文中讨论它,但是如果您有兴趣,请在评论部分告诉我。记住这一点,让我们继续!

在这个领域和上下文中,数据清理意味着:删除(或转换)任何具有恶意目的的数据(即字符)。换句话说,您希望数据在验证之前是安全的。但是,您应该始终考虑可用性和实用的方法。鉴于我们正在处理电子邮件地址输入字段,我们不能简单地从收到的输入中删除数据。因此,如果数据不合适,我们应该停止与数据相关的进程并向用户返回消息;消息应该提醒用户可以使用的字符(正面反馈)和/或在这种情况下,电子邮件地址的标准语法。

请记住,与数据相关的进程应始终在受信任的系统(例如后端)上进行,否则它们可能会被恶意用户/脚本禁用/绕过(例如前端)。

输入示例:

example<script>alert('Tacos!');</script>@domain.com


通过阅读上面的输入,我们可以直接判断这肯定不是电子邮件地址,而且它正试图通过数据传递脚本有效负载。但是,这种欣赏来自我们的视觉解读。

从编码的角度来看,应该通过应用以下前提来实施数据清理:什么是允许的?这也称为允许列表。建议在阻止列表的前提下使用这种方法:什么是不允许的?原因很简单:对于允许的和不允许的,你有一组已知的有限可能性。

在任何编码语言中实现允许列表的最有效方法可能是使用正则表达式。

Golang 正则表达式示例:

package main

import (
	"fmt"
	"regexp"
)

func main() {
	input := "example<script>alert('Injected!');</script>@domain.com"

	re := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")

	fmt.Printf("Pattern: %v\n", re.String()) // print pattern	
	fmt.Printf("\nEmail: %v :%v\n", input, re.MatchString(input))
}

结果):

Pattern: ^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$

Email: example<script>alert('Injected!');</script>@domain.com :false

是的,所以,我们已经看到了如何使用 Golang 实现正则表达式。

是为了数据清理吗?不。

当您处理现实世界的应用程序时,您将不得不捕获其他类型的输入,具体取决于您的应用程序。因此,这里有六点需要考虑:

  • 在执行数据清理之前始终执行数据规范化;规范化很重要,因为在 Unicode 中,同一个字符可以有许多不同的表示形式,这会对数据清理过程产生负面影响;请查看 Golang 编码示例的链接。
  • 如果您接收路径(例如目录、链接等)作为输入,请在执行数据清理之前对其进行规范化;绝对或相对路径可用于恶意目的,因为它们可能包含文件链接,例如符号(软)链接、硬链接、快捷方式、阴影、别名和连接;因此,它们应该得到彻底解决;此外,请记住您的方法并验证它是否适用于预期的操作系统。

Golang 规范化示例:

package main

import (
	"fmt"
	"path/filepath"
)

func main() {
	input := "/usr/local/bin/kubectl"
	
	resolvedPath, _ := filepath.EvalSymlinks(input)

	fmt.Printf("Input: %v :%v\n", input, resolvedPath)
}

结果):

Input: /usr/local/bin/kubectl :/usr/local/Cellar/kubernetes-cli/1.17.2/bin/kubectl

 

  • “默认情况下所有输入数据都是不安全的”假设也适用于隐藏的表单字段、URL、HTTP 标头内容等。
  • 请注意,可以尝试对您的系统进行双重编码或其他形式的混淆攻击,并且可以通过规范化来解决它们。
  • 检查空字节:<%00>。
  • 检查换行符。

数据清理结束后,应进行输入验证。正如术语明确指出的那样,输入验证确保数据遵守适用于/定义为一种输入类型的规则。这意味着什么……如果一种输入与知识领域或业务规则相关联,那么,应该强制执行其中规定的任何特征。对于我们根据存储能力或处理目的定义的规则也是如此:例如我们允许为此类输入存储多少个字符。

为了理解输入验证,让我们考虑一个有表单的网站,并且在表单中只有一个名字的输入字段。同样,为了简单起见,我们将对表单的使用/填写做出与之前相同的假设。

输入示例:

John Alejandro Dumas Patricio O'Neil


这是一个非常有趣的例子。技术已经足以连接世界大部分地区,这在我们提供服务时产生了巨大影响。让我们从数据清理的角度开始。一些实现不允许使用 <'> 作为名字的一部分。就个人而言,我认为这没有考虑到全球化和连通性。用户不需要从外国访问网站,他/她的名字就不会符合“预期”的限制。我鼓励您阅读这篇文章,以便您做出适当的决定:世界各地的个人姓名

话虽如此,在数据清理之后可以进行什么样的输入验证?假设我们的数据存储中的名字有 30 个字符空间的限制。这个输入是否有效?

Golang 示例:

package main

import (
	"fmt"
)

func main() {
	input := "John Alejandro Dumas Patricio O'Neil"

	fmt.Printf("First name: %v :%d\n", input, len(input))
}

结果):

First name: John Alejandro Dumas Patricio O'Neil :36


从上面的结果中,我们可以看出它不是一个有效的输入,因为它超过了允许的最大字符数。请注意,上面的实现是一个非常简单的实现,它也可以使用正则表达式来执行。我认为代码应该尽可能简单,但这取决于你想要实现你的方法有多“花哨”。

话虽如此,在输入验证方面还有几点需要您考虑:

  1. 无论您实现什么输入验证功能/方法,都让它们集中可用;这也适用于数据清理;理由是您希望在您的应用程序和团队中使用相同级别的标准。
  2. 所有验证失败都应导致输入拒绝。
  3. 验证数据类型和数据范围;确保在适当的时候进行边界检查。

结论


我们现在已经涵盖了这两个术语,那么它们与 OWASP Top 10 有什么关系呢?

输入验证和数据清理与以下内容直接相关:

  1. A1:注射
  2. A4:XML 外部实体 (XXE)
  3. A7:跨站脚本(XSS)

在下一篇文章中,我将介绍解决输出编码后的 OWASP 十大风险。

延伸阅读:

 

希望您发现本教程很有用,如果您这样做了,或者如果您需要进一步的帮助,请不要犹豫,在下面的评论部分告诉我!再次感谢您的宝贵时间!

文章链接