Bard、ChatGPT 和 Bing Chat 等人工智能工具是目前正在崛起的大型语言模型 (LLM)类别中的知名工具。
法学硕士接受过大量数据集的培训,能够使用日常人类语言作为聊天提示进行交流。鉴于法学硕士的灵活性和潜力,公司正在将其整合到科技行业内的许多工作流程中,以使我们的生活变得更好、更轻松。主要的人工智能工作流集成之一是自动完成代码插件,它提供了预见和生成匹配代码模式的能力,以便更快、更高效地编码。
目前AI代码生成工具市场上有很多玩家,包括GitHub Copilot 、 Amazon CodeWhisperer 、 Google Cloud Code (Duet AI) 、 Blackbox AI Code Generation等。这篇博文概述了开发人员在使用此类工具时面临的常见安全陷阱的示例。
本文的目的是提高认识并强调自动生成的代码不能盲目信任,仍然需要进行安全审查以避免引入软件漏洞。
注意:一些供应商建议他们的自动完成工具可能包含不安全的代码片段。
许多文章已经强调自动完成工具会产生已知的漏洞,例如IDOR 、SQL 注入和 XSS。这篇文章将重点介绍更多依赖于代码上下文的其他漏洞类型,其中人工智能自动完成很有可能将错误插入到您的代码中。
安全和人工智能代码生成工具针对代码训练的人工智能模型通常在大型代码库上进行训练,其主要任务是生成功能代码。
许多安全问题都有已知且定义的缓解方法,而不会影响性能(例如,可以通过不将用户输入/参数直接连接到查询中来避免 SQL 注入),因此可以根除,因为没有理由编写以不安全的方式编写代码。
然而,许多安全问题是与上下文相关的,当作为内部功能实现时可能是完全安全的,而当面对来自客户端的外部输入时,就会成为漏洞,因此很难通过人工智能的学习数据集来区分。
在此类漏洞中,代码的具体用途通常取决于代码的其他部分和应用程序的总体目的。
以下是我们测试的一些常见用例和示例,展示了其中一些类型的漏洞:
在了解人工智能代码生成工具如何处理上述情况后,我们尝试测试它们创建适当的安全过滤器和其他缓解机制的能力。
以下是我们通过故意请求安全代码进行测试的一些常见用例和示例:
许多应用程序都包含根据文件名将文件取回给用户的代码。使用目录遍历读取服务器上的任意文件是不良行为者获取未经授权信息的常用方法。
在取回文件之前清理来自用户的文件名/文件路径输入似乎是显而易见的,但我们认为对于在通用代码库上训练的 AI 模型来说这是一项艰巨的任务 - 这是因为某些数据集不需要使用经过净化的路径作为其功能的一部分(甚至可能会降低性能或损害功能),或者不会受到未经净化的路径的损害,因为它们仅供内部使用。
AI 工具建议(Google Cloud Code - Duet AI):让我们尝试使用 Google Cloud Code - Duet AI 自动完成功能,根据用户输入生成基本的文件获取功能。我们将让 Duet AI 根据我们的函数和路线名称自动完成我们的代码 -
正如我们在灰色中看到的,自动完成建议是使用 Flask 的“ send_file ”函数,这是 Flask 的基本文件处理函数。
但即使 Flask 的文档声明“永远不要传递用户提供的文件路径”,这意味着在将用户输入直接插入到“send_file”函数时,用户将能够读取文件系统上的任何文件,例如通过使用目录遍历字符例如文件名中的“../”。
正确实施:
将“send_file”函数替换为 Flask 的安全“send_from_directory”函数将降低目录遍历风险。它只允许从特定的硬编码目录(在本例中为“导出”)获取文件。
在某些编程语言中,例如 PHP 和 NodeJS,可以在两种不同类型的变量之间进行比较,并且程序将在幕后进行类型转换来完成该操作。
松散比较不会在幕后检查变量的类型,因此更容易受到类型杂耍攻击。然而,使用松散比较本身并不被认为是不安全的编码实践——它取决于代码的上下文。同样,人工智能模型很难区分这些情况。
AI 工具建议 (Amazon CodeWhisperer): PHP 类型杂耍的最常见示例是秘密令牌/哈希的松散比较,其中通过 JSON 请求(可控制输入类型)读取的用户输入达到松散比较(“ ” )而不是严格的(“ =”)。
看起来,CodeWhisperer 在自动完成建议中生成宽松的比较条件:
Secret_token 的松散比较允许对手绕过比较 $data[“secret_token”] == “secret_token”。当提交“secret_token”值为 True(布尔值)的 JSON 对象时,松散比较结果也为 True (即使在较新的 PHP 版本上)。
即使当“提示”工具(使用注释)“安全”地编写检查时,它也不会生成包含严格比较的代码片段 -
正确实施:
仅当指定严格比较(“===”)时,我们才能免受类型杂耍攻击。
“忘记密码”机制中的漏洞在 SaaS 应用程序中非常常见,也是 CVE 背后的根本原因,例如
具体来说,当用户输入在比较表达式中为大写或小写,而其原始值也在代码中使用时,就会出现 Unicode 大小写映射冲突漏洞。一些不同的字符在转换时会产生相同的字符代码。例如,“ß”和“SS”大写时都会转换为“0x00DF”。
此类情况通常用于通过欺骗比较检查并随后使用与预期不同的原始值来绕过/误导某些安全检查。这些情况很难掌握,尤其是当可以实现多种实现时。
AI工具建议(GitHub Copilot):特别容易受到此漏洞类型影响的机制是忘记密码机制。在执行检查时,通常会将用户的输入标准化为大写或小写(在电子邮件地址或域名中),以避免不受欢迎的不匹配。
例如,查看 Copilot 生成的忘记密码功能代码(如下)表明它容易受到此问题的影响。
Copilot 的代码首先查找小写的电子邮件地址:
然后,在完成其余过程时,它会建议以下内容:
正如我们所看到的,自动完成建议会生成一个随机密码并将其发送到用户的电子邮件地址。但是,它最初使用用户电子邮件的小写版本来获取用户帐户,然后继续“按原样”使用原始用户的电子邮件(不进行小写转换)作为恢复电子邮件的目标。
例如,假设攻击者拥有“[email protected]”的收件箱(“K”是开尔文符号unicode 字符),并使用此电子邮件进行“忘记密码”机制。电子邮件“[email protected]”和“[email protected]”并不等同于“原样”,但在小写转换后它们将是 -
使用电子邮件地址“[email protected]”将获取“[email protected]”的帐户信息,但重置密码将发送到攻击者的收件箱(“[email protected]”),从而允许接管“ [email protected]”帐户。
正确实施:用于获取用户帐户的电子邮件地址必须与恢复电子邮件地址完全相同(即 send_email 参数也将使用 email.lower())。
另外,作为预防措施,建议使用服务器中已存储的值,而不是用户提供的值。
另一个可能使自动完成工具感到困惑的主题是编写配置文件,尤其是对于复杂的云基础设施。
主要原因是确实有安全最佳实践指南,但并不是每个人都遵循它们,在某些情况下,需要违背它们。此类代码样本可能会进入人工智能训练数据集。因此,某些自动完成输出将违反建议的安全准则。
在下面的示例中,我们将为Kubernetes Pod (Kubernetes 中最小的执行单元)创建一个配置文件。
AI工具建议(Blackbox AI代码生成):在这个例子中,我们开始创建一个Pod配置文件。
该建议创建功能部分并向容器添加 Linux 内核功能 (SYS_ADMIN),这本质上相当于以 root 用户身份运行 pod。
正确的实现:那么省略 securityContext 是不是更好?否 - 因为默认情况下将添加许多其他功能,这违反了最小特权原则。
根据Docker 安全最佳实践,最安全的设置是首先删除所有 Linux 内核功能,然后再添加容器所需的功能。
为了解决上述问题,功能部分首先删除所有功能,然后逐渐添加容器所需的功能。
用例:配置对象 - 不一致导致不安全的反序列化
许多漏洞需要定义一个配置对象,该对象决定应用程序如何处理所需的组件。
我们选择的示例是Newtonsoft 的 C# 中的 JSON反序列化库,它可以安全或不安全地使用,具体取决于 JsonSerializerSettings 对象的配置,特别是TypeNameHandling 。
当我们测试反序列化代码时,我们注意到配置对象的定义是相当随机的,细微的变化可能会导致生成不同的配置集。
AI 工具建议 (Amazon CodeWhisperer):以下示例显示仅基于开发人员使用的方法名称的不一致行为:
我们可以看到两种不同的配置,由于 TypeNameHandling 属性为“Auto”和“ALL”,都会导致不安全的 JSON 反序列化。这些属性允许 JSON 文档定义反序列化类 - 让攻击者反序列化任意类。这可以轻松地用于远程代码执行,例如通过反序列化“System.Diagnostics.Process”类。开发人员原始代码的唯一区别是方法名称。
正确实施:
默认情况下,JsonSerializerSettings 对象使用“TypeNameHandling = TypeNameHandling.None”实例化,这被认为是安全的。因此,我们使用 JsonSerializerSettings 的默认构造函数,这将产生一个安全的 TypeNameHandling 值。
AI工具建议(GitHub Copilot):
我们可以看到,安全检查确实很简单,只是避免了进行目录遍历尝试所需的点-点-斜杠序列。路径参数可以是绝对路径 - 指向系统上任何所需的路径(例如,/etc/passwd),这违背了安全检查的目的。
正确实施:
在这里,secure_file_read 函数获取一个(相对)文件名参数以及文件名应驻留(且不应转义)的目录 (safe_dir)。它通过连接 safe_dir 和文件名创建绝对路径,并通过调用 realpath 获取其规范化形式。然后它获取 safe_dir 文件夹的规范化形式。然后,仅当规范化路径以规范化 safe_dir 开头时才返回文件内容。
AI 工具建议(Blackbox AI 代码生成):在以下示例中,我们要求 AI 自动完成器生成一个负责过滤危险文件扩展名的函数。
建议的 Python 代码获取文件名,分隔扩展名,并将其与危险扩展名列表进行比较。
乍一看,这段代码片段看起来很安全。但是, Windows 文件名约定禁止以点结尾的文件名,并且在文件创建期间提供以点 (“.”) 结尾的文件名时,该点将被丢弃。这意味着,当提交带有恶意扩展名并后跟一个点(例如“example.exe”)的文件时,在 Windows 上可以绕过生成的过滤器。
正确的实现:通常更好的方法是使用白名单,它仅根据允许的扩展名检查文件扩展名。然而,由于这些工具采用了黑名单方法(在某些情况下需要),我们将概述一个更安全的黑名单实现:
在下面的代码片段中,我们通过从所有非字母数字字符中剥离扩展名并将其连接到新生成的文件名来剥离用户对输入的尽可能多的控制。
底线 - 谨慎使用自动协同程序员非常适合提出想法、自动完成代码和加快软件开发过程。我们预计这些工具会随着时间的推移而不断发展,但就目前而言,正如本文中的用例所示,自动完成的人工智能解决方案需要克服许多挑战,直到我们能够放松心情并信任它们的输出,而无需仔细检查错误。
降低引入漏洞可能性的一种可能方法是故意要求人工智能代码生成工具生成安全代码。尽管这在某些用例中可能有用,但它并不是万无一失的——“看似安全”的输出仍然应该由精通软件安全的人手动审查。
JFrog 安全研究团队建议为了帮助保护您的软件,我们建议手动检查 AI 生成工具生成的代码,并结合安全解决方案,例如静态应用程序安全测试 (SAST),它可以在您运行时可靠地发现漏洞。如上例所示,SAST 工具可以发出警报并捕获用例 #3 中描述的 unicode 大小写映射冲突。这种主动的方法对于确保软件的安全至关重要。
及时了解 JFrog 安全研究的最新动态安全研究团队的发现和研究对于提高 JFrog 平台的应用软件安全能力发挥着重要作用。
在我们的研究网站和 X @JFrogSecurity上关注 JFrog 安全研究团队的最新发现和技术更新。