C3:验证所有输入并处理异常
描述
输入验证是一种编程技术,用于确保只有格式正确的数据才能进入软件系统组件。当注入攻击针对客户端(例如基于JavaScript的攻击)时,Web服务器可以在将攻击者提供的数据转发给客户端之前对其进行引用/编码。
如果应用程序将数据输入误认为是可执行命令,并且在输入验证被遗忘或实施错误时,注入攻击通常会发生。例如,假设一个Web应用程序接受用户输入的电子邮件地址。电子邮件地址将是预期的“数据”。攻击者现在寻找方法来混淆应用程序,使其将这些(假定)数据作为命令执行。不同的注入攻击针对不同的区域
- 当攻击者诱骗应用程序将用户输入(数据)解释为SQL命令(或其部分)时,就会发生SQL注入攻击。注入的命令在数据库服务器内部执行。
- 如果应用程序将用户数据与在Web应用程序服务器/主机上执行的命令混淆,则会发生远程命令注入(RCE)。服务器端模板注入是应用程序服务器内执行的注入的另一个示例。
- 当发生JavaScript注入时,Web应用程序已接受用户数据,但被迫将该数据作为代码执行。注入的JavaScript代码通常在另一个用户的Web浏览器中执行,因此它不直接攻击Web服务器,而是攻击其他用户。
语法和语义有效性
应用程序在使用数据之前(包括将其显示给用户),应检查数据是否在语法上和语义上有效(按此顺序)。
-
语法有效性意味着数据处于预期的格式。例如,应用程序可能允许用户选择一个四位数的“帐户ID”来执行某些操作。应用程序应假定用户正在输入SQL注入有效载荷,并检查用户输入的数据长度是否恰好是四位数字,并且只包含数字(此外还要利用适当的查询参数化)。
-
语义有效性包括只接受应用程序功能和上下文可接受范围内的输入。例如,在选择日期范围时,开始日期必须在结束日期之前。
威胁
- 攻击者可以通过提交恶意输入来操纵数据库查询,从而利用SQL注入漏洞,可能导致未经授权访问敏感数据。
- 攻击者可以通过将恶意脚本注入到网页中来执行跨站脚本(XSS)攻击,这些脚本随后在其他用户的浏览器中执行,可能窃取会话令牌或个人信息。
- 攻击者可以通过向系统调用或API注入恶意命令来远程执行任意代码,可能控制目标系统。
- 攻击者可以通过提供超出预期长度的输入来触发缓冲区溢出错误,可能覆盖内存并执行任意代码。
- 攻击者可以通过向系统提供畸形或过量的输入来发起拒绝服务攻击,可能导致合法用户无法使用服务。
- 攻击者可以通过路径遍历攻击访问未经授权的文件和目录,可能暴露敏感系统文件或配置数据。
- 攻击者可以将恶意载荷插入XML文档中,以利用XML解析漏洞,可能导致信息泄露或系统受损。
- 攻击者可以将恶意模板注入到服务器端模板引擎中,可能在服务器上实现远程代码执行。
- 攻击者可以通过HTTP参数污染攻击混淆应用程序并绕过安全控制,可能操纵应用程序逻辑或访问受限功能。
实施
防范注入攻击通常基于深度防御方法,并结合了输入过滤、输出转义和强化机制的利用。前两者仅取决于已实施的安全措施,而后者主要取决于客户端支持,例如,在防范XSS时,无论使用何种Web浏览器,从输入中过滤XSS并对服务器端输出数据进行转义都可以防止XSS;添加内容安全策略(Content-Security-Policy)可以防止XSS,但这仅限于用户的浏览器支持它。因此,安全性绝不能仅依赖于可选的强化措施。
防止恶意数据进入系统
永远不要相信提供的数据!检查所有数据是否存在恶意模式,或者更好的是,根据白名单检查所有数据。
白名单 vs 黑名单
执行语法验证有两种通用方法,通常称为白名单和黑名单
- 黑名单或黑名单验证试图检查给定数据是否不包含“已知不良”内容。例如,Web应用程序可能会阻止包含精确文本`<SCRIPT>`的输入,以帮助防止XSS。然而,这种防御可以通过小写脚本标签或混合大小写的脚本标签来规避。
- 白名单或白名单验证试图检查给定数据是否符合一组“已知良好”的规则。例如,美国州的白名单验证规则将是仅为有效美国州之一的2个字母代码。白名单是推荐的最小方法。黑名单容易出错,可以通过各种规避技术绕过,并且当它自身依赖时可能很危险。尽管黑名单经常被规避,但它有助于检测明显的攻击。因此,白名单通过确保数据的语法和语义正确性来帮助限制攻击面,而黑名单有助于检测并可能阻止明显的攻击。
客户端和服务器端验证
为了安全起见,始终在服务器端执行输入验证。虽然客户端验证对于功能和安全目的都很有用,但它很容易被绕过。因此,客户端验证是出于可用性目的而执行的,但应用程序的安全性绝不能依赖于它。例如,JavaScript验证可能会提醒用户某个特定字段必须由数字组成。但是,服务器端应用程序必须验证提交的数据只包含该功能相应数字范围内的数字。同时使用客户端和服务器端验证的另一个好处是,任何服务器端验证警告都可以被记录下来,以告知操作人员存在潜在的黑客,因为客户端验证已被绕过。
正则表达式
正则表达式提供了一种检查数据是否匹配特定模式的方法。让我们从一个基本示例开始。以下正则表达式定义了一个白名单规则来验证用户名。
此正则表达式仅允许小写字母、数字和下划线字符。用户名长度也限制在3到16个字符之间。
注意:拒绝服务的可能性
创建正则表达式时应谨慎。设计不当的表达式可能导致潜在的拒绝服务状况(又称ReDoS)。可以使用各种工具进行测试,以验证正则表达式不易受到ReDoS攻击。
注意:复杂性
正则表达式只是实现验证的一种方式。对于某些开发人员来说,正则表达式可能难以维护或理解。其他验证替代方案涉及以编程方式编写验证方法,这对于某些开发人员来说可能更容易维护。
意外用户输入(批量赋值)
一些框架支持将HTTP请求参数自动绑定到应用程序使用的服务器端对象。这种自动绑定功能可能允许攻击者更新原本不应被修改的服务器端对象。攻击者可能通过此功能修改其访问控制级别或规避应用程序的预期业务逻辑。
这种攻击有多种名称,包括:批量赋值、自动绑定和对象注入。
举一个简单的例子,如果用户对象有一个特权字段,用于指定用户在应用程序中的特权级别,恶意用户可以寻找修改用户数据的页面,并在发送的HTTP参数中添加privilege=admin。如果自动绑定以不安全的方式启用,代表用户的服务器端对象将相应地被修改。
可以使用两种方法来处理此问题
- 避免直接绑定输入,而是使用数据传输对象(DTO)。
- 启用自动绑定,但为每个页面或功能设置白名单规则,以定义允许自动绑定的字段。
更多示例可在OWASP批量赋值备忘录中找到。
输入验证的局限性
输入验证并非总能使数据“安全”,因为某些复杂的输入形式可能“有效”但仍然危险。例如,一个有效的电子邮件地址可能包含SQL注入攻击,或者一个有效的URL可能包含跨站脚本攻击。除了输入验证之外,还应始终对数据应用其他防御措施,例如查询参数化或转义。
使用维护数据与命令分离的机制
即使恶意数据通过了输入检查,应用程序也可以通过永不将这些恶意数据作为命令/代码执行来防止注入攻击。多种措施可以实现这一目标,其中大多数都依赖于具体技术。例如
- 通过SQL使用关系数据库时,请利用预处理语句(Prepared Statements)。SQL注入攻击通常发生在攻击者能够提供输入数据,使其从通过字符串连接创建的SQL命令中“逃逸”出来。使用预处理语句允许计算机自动编码输入数据,从而使其无法从命令模板中“逃逸”。
- 使用ORM时,请确保您了解对象如何映射到SQL命令。虽然其间接层可以防止常见的SQL注入,但特殊准备的攻击通常仍然可行。
- 服务器端模板注入(SSTI)利用服务器端模板引擎动态生成内容,然后显示给用户。SSTI引擎通常允许配置沙盒,即只允许执行有限数量的方法。
- 将用户输入作为参数执行系统命令容易受到注入攻击。如果可行,应避免这样做。
JavaScript注入攻击
基于JavaScript的注入攻击(XSS)是一个特殊情况。注入的恶意代码通常在受害者的浏览器中执行。通常,攻击者试图从浏览器中窃取用户的会话信息,而不是直接执行命令(就像他们在服务器端那样)。除了服务器端输入过滤和输出转义之外,还可以采取多种客户端强化措施(这些措施还可防御DOM型XSS这种特殊情况,其中不涉及服务器端逻辑,因此无法过滤恶意代码)。
- 将敏感cookie标记为httpOnly,以便JavaScript无法访问它们
- 利用内容安全策略(Content-Security-Policy)来减少基于JavaScript的攻击的攻击面
- 使用默认安全的框架,如Angular
验证和清理HTML
考虑一个需要接受用户HTML输入(通过将内容表示为HTML的所见即所得编辑器或直接接受HTML输入的特性)的应用程序。在这种情况下,验证或转义将无济于事。
- 正则表达式不足以理解HTML5的复杂性。
- 编码或转义HTML无济于事,因为它会导致HTML无法正常渲染。
因此,您需要一个库来解析和清理HTML格式的文本。有关HTML清理的更多信息,请参阅XSS防范备忘录中的HTML清理部分。
特殊情况:反序列化期间的数据验证
某些形式的输入非常复杂,验证只能对应用程序提供最低限度的保护。例如,反序列化不受信任的数据或可能被攻击者操纵的数据是危险的。唯一安全的架构模式是不接受来自不受信任源的序列化对象,或者仅以有限的能力反序列化简单数据类型。在可能的情况下,您应避免处理序列化数据格式,并使用更容易防御的格式,如JSON。
如果不可能,则在处理序列化数据时考虑一系列验证防御措施。
- 对序列化对象实施完整性检查和加密,以防止恶意对象创建或数据篡改。
- 在对象创建之前的反序列化期间强制执行严格的类型约束;通常代码期望一组可定义的类。已证明存在绕过此技术的案例。
- 隔离反序列化代码,使其在极低权限的环境中运行,例如临时容器。
- 记录安全反序列化异常和失败,例如传入类型不是预期类型,或反序列化抛出异常的情况。
- 限制或监控来自反序列化容器或服务器的入站和出站网络连接。
- 监控反序列化,如果用户持续进行反序列化则发出警报。
防止的漏洞
- 输入验证减少了应用程序的攻击面,有时可以使针对应用程序的攻击变得更加困难。
- 输入验证是一种为特定形式的数据提供安全性、针对特定攻击的技术,不能可靠地作为通用安全规则应用。
- 输入验证不应作为防止XSS、SQL注入及其他攻击的主要方法。
- 2023 CWE Top 25 - 3 SQL命令中特殊元素的错误中和('SQL注入')
- 2023 CWE Top 25 - 5 操作系统命令中特殊元素的错误中和('操作系统命令注入')
- 2023 CWE Top 25 - 16 命令中特殊元素的错误中和('命令注入')
- 2023 CWE Top 25 - 23 代码生成控制不当('代码注入')
参考资料
关于输入验证
工具
输入验证协助
- OWASP Java HTML清理器项目
- Java JSR-303/JSR-349 Bean验证
- Java Hibernate ValidatorApache Commons ValidatorPHP的过滤函数
注入攻击测试
强化安全协助