yohgaki / validate-php-scr
简单的通用输入数据验证框架。脚本版本。
Requires
- php: >=7.0
- ext-bcmath: *
Suggests
- ext-gmp: Better integer handling
- ext-mbstring: Better multibyte char handling
This package is auto-updated.
Last update: 2024-09-08 15:55:55 UTC
README
"Validate"是一个旨在为“CERT安全编码”和“设计由合同”(DbC)有用的“输入数据验证框架”。“Validate”旨在开发符合OWASP TOP 10 A10:2017漏洞的应用程序。“Validate”计划作为PHP的C模块实现。
1. 验证输入。验证来自所有不受信任数据源(如命令行参数、网络接口、环境变量和用户控制的文件[Seacord 05])的输入。适当的输入验证可以消除绝大多数软件漏洞。对大多数外部数据源保持怀疑态度。
针对PHP的验证是为了帮助“标准输入验证”。
使用标准输入验证机制来验证以下所有输入:
- 长度
- 输入类型
- 语法
- 缺少或多余的输入
- 相关字段的一致性
- 业务规则
基本设计
- 框架 - "Validate"是框架,不是自带的库。提供简单而灵活的输入数据验证。
- 安全 - 没有不安全的默认设置。必须明确指定一切。即白名单。
- 快速 & 简单 - 定义数据验证规则并使用它们。无需代码执行来构建验证规则。
- 易于使用 - 简单的PHP数组规则指定。用于复杂输入的纯PHP代码。
- 本地类型 - 默认返回本地类型的数据。消除类型转换,并帮助使用类型提示更快地执行PHP代码。
"Validate"非常灵活,能够执行任何数据验证,包括带有多阶段验证的HTTP头/查询参数验证、HTML表单验证、JSON数据验证等。尽管"Validate"旨在一次性验证所有输入,但也可能逐个验证值。
"字符串"对于输入验证至关重要,"Validate"对字符串验证非常严格。许多应用程序没有验证“字符编码”和“长度”。“Validate”默认强制始终进行验证。默认情况下不允许Unicode控制字符,这可能导致问题。
要求
- PHP 8.0及以上。
- PHP 7.0及以上。
- BCMath模块。
推荐
- PHP 7.2及以上 - 更新的mbstring提供更好的性能。
- mbstring模块 - mb_ord()提高性能,更多支持的编码和更好的编码检查。
- GMP模块 - "Validate"也支持GMP整数。
基本行为
- validate()现在不允许任何内容,除非明确指定。即严格的白名单。
- validate()在可能的情况下将输入值转换为本地类型。例如,'123'转换为int。'yes'/'no'转换为bool。
- validate()递归地处理输入值和验证规范。即接受任何输入值(标量/数组/对象)。
- validate()默认检查验证规范格式。即对于生产环境禁用规范格式检查。
- validate()将验证状态存储到上下文中。例如,下面的示例中的$ctx。
- validate()默认抛出InvalidArgumentException。
提示
- 对于应用程序业务逻辑数据验证,禁用异常并设置'error_message'选项。
- 定义自己的'filter'进行标准化。
- 定义自己的'key_callback'进行自定义数组键验证。
- 使用VALIDATE_CALLBACK和'callback'进行复杂验证。
示例 #1:带异常的单值验证
src/examples/91-example.php
<?php require_once __DIR__.'/../validate_func.php'; // Define basic type specifications array $B. You can define any validation rule as you like. require_once __DIR__.'/../lib/basic_types.php'; // Validate domain name $domain = 'es-i.jp'; $domain = validate($ctx, $domain, $B['fqdn']); // Validate record ID $id = '1234'; $id = validate($ctx, $id, $B['uint32']); // Check results var_dump($domain, $id);
验证错误会导致异常。应用程序输入数据验证错误应该通过异常处理,而不需要用户交互。
注意:有效输入是应用程序应该处理的任何输入。即 输入错误是应用程序的有效输入。
示例 #2:不带异常的单值验证
src/examples/92-example.php
<?php require_once __DIR__.'/../validate_func.php'; require_once __DIR__.'/../lib/basic_types.php'; // Defines basic type array $B $func_opts = VALIDATE_OPT_DISABLE_EXCEPTION; // Validate domain name w/o exception $domain = 'es-i.jp'; $domain = validate($ctx, $domain, $B['fqdn'], $func_opts); // Validate record ID $id = '1234'; $id = validate($ctx, $id, $B['uint32'], $func_opts); if (validate_get_status($ctx) == false) { // Check last validation error } // Get all user error $errors = validate_get_user_errors($ctx); //Check results var_dump($domain, $id, $errors);
验证错误存储在$ctx中。应用程序业务逻辑数据验证错误应该在没有错误/异常的情况下处理,以便进行交互式错误处理。
示例 #3:一次性进行多个值验证。
src/examples/93-example.php
<?php // Simple "username" and "email" form validation example. // "Validate" is suitable for "From Validations" also. require_once __DIR__.'/../validate_func.php'; require_once __DIR__.'/../lib/basic_types.php'; // Defines basic type array $B // In practice, you would define all inputs specifications at central repository. // If your web app does not have strict client side validations, you will need // "Input validation spec" AND "Business logic(Form) validation spec". // If client JavaScript has validation $username = [ VALIDATE_STRING, // "username" is string VALIDATE_STRING_ALNUM, // "username" has only alphanumeric chars. ['min'=> 6, 'max'=> 40, // "username" can be 6 to 40 chars. 'error_message'=>'Username is 6 to 40 chars. Alphanumeric char only.'] ]; // "Validate" can be extend by callbacks. $email = [ VALIDATE_CALLBACK, // "email" is complex, so write PHP script for it. VALIDATE_CALLBACK_ALNUM, // Allow alpha numeric chars. ['min'=> 6, 'max'=> 256, 'ascii'=>'@._-', // Allow 6 to 256 chars and additional '@._-' 'error_message'=>'Please enter valid email address. We only accepts address with DNS MX record.', 'callback'=> function($ctx, &$result, $input) { // Let's define rules by PHP function. $parts = explode('@', $input); if (count($parts) > 2) { // Chars/min/max is already validated. $err = "Only one '@' is allowed."; // This could be i18n function for multilingual sites. validate_error($ctx, $err); return false; } if (!dns_get_mx($parts[1], $mx)) { $err = "Sorry, we only allow hosts with MX record."; validate_error($ctx, $err); return false; } return true; }] ]; $spec = [ // Combine predefined parameter spec into one spec. VALIDATE_ARRAY, VALIDATE_FLAG_NONE, ['min'=>2, 'max'=>10], // Inputs must have 2 to 10 elements. [ // Simply reuse predefined spec for parameters. "username" => $username, "email" => $email, // You can validate $_GET/$_POST/$_COOKIE/$_SERVER/$_FILES at once by nesting. ] ]; $inputs = [ 'username' => 'yohgaki', 'email' => 'yohgaki@ohgaki.net' ]; $func_opts = VALIDATE_OPT_DISABLE_EXCEPTION; // Disable exception, to check errors, etc. $results = validate($ctx, $inputs, $spec, $func_opts); // Now, let's validate and done. // Check results var_dump(validate_get_status($ctx)); // $results is NULL when error. validate_get_status() can be used also. var_dump($results, $inputs); // $inputs contains unvalidated values. var_dump(validate_get_user_errors($ctx)); // Get user errors. var_dump(validate_get_system_errors($ctx)); // Get system errors.
"Validate"会从$inputs中删除验证过的元素。您可以通过后续验证验证剩余元素。
这里有一个更实际的工作示例
示例 #4:验证所有HTTP头
src/examples/94-example.php
<?php require_once __DIR__.'/../validate_func.php'; require_once __DIR__.'/../lib/basic_types.php'; // Defines $B (basic type) array $request_headers_orig = ['a'=>'abc', 'b'=>'456']; //apache_request_headers(); // Get request headers // Check cookie and user agent. Allow undefined and extra headers. $B['cookie'][VALIDATE_FLAGS] |= VALIDATE_FLAG_UNDEFINED; // Allow undefined(optional) $B['user-agent'][VALIDATE_FLAGS] |= VALIDATE_FLAG_UNDEFINED_TO_DEFAULT; // Allow undefined and set default $B['user-agent'][VALIDATE_OPTIONS]['default'] = ''; $B['user-agent'][VALIDATE_OPTIONS]['min'] = 0; // Allow 0 length(empty) $spec1 = [ // Explicit validations VALIDATE_ARRAY, VALIDATE_FLAG_NONE, ['min'=>2, 'max'=>20], // Inputs must have 2 to 20 elements. [ 'Cookie' => $B['cookie'], 'User-Agent' => $B['user-agent'], ] ]; // validate() removes validated values from $request_headers_orig $request_headers = validate($ctx, $request_headers_orig, $spec1); // Check the rest of headers. // Allow array 'header512' strings and ALNUM + '_' + '-' keys $B['header512'][VALIDATE_FLAGS] |= VALIDATE_FLAG_ARRAY | VALIDATE_FLAG_ARRAY_KEY_ALNUM; $B['header512'][VALIDATE_OPTIONS]['min'] = 0; // Allow 0 length(empty) headers $B['header512'][VALIDATE_OPTIONS]['amin'] = 0; // Allow 0 extra headers $B['header512'][VALIDATE_OPTIONS]['amax'] = 20; // Allow 20 extra headers $spec2 = $B['header512']; // $request_headers has only validated values. No control chars nor multibyte chars. $request_headers += validate($ctx, $request_headers_orig, $spec2); // Check results var_dump($request_headers, $request_headers_orig);
OWASP TOP 10 A10:2017 要求验证所有输入。尽管您最好对头部的验证更严格,但这种验证符合OWASP TOP 10 A10:2017的HTTP请求头部验证。
"应用程序输入"和"业务逻辑"数据验证基础
应用程序输入数据验证和业务逻辑数据验证是 两种不同的验证。
应用程序输入数据验证
重要:用户错误是有效输入。
应用程序输入数据应尽可能快地验证,以最大限度地减少不良行为/漏洞。应用程序INPUT数据验证必须验证所有输入对应用程序都有效。例如,如果ID是1000到INT_MAX之间的数字,则只能接受1000个来自INT_MAX的数字。如果字符串是用于名称,则该字符串不应超过512字节,除非攻击者正在篡改您的应用程序。如果您的应用程序通过客户端JavaScript限制名称长度为100,则不应接受超过100的字符串。不要忘记检查参数太少/太多,因为这是一个“标准输入验证”要求之一。
所有应用程序输入数据都必须进行验证,并且验证应在 应用程序信任边界 上进行。应用程序INPUT数据验证确保“值具有正确的 形式”。例如,长度、范围、编码、使用的字符、特定的格式,如日期、电话、邮编。两种 正确和“输入错误”的值 都是有效输入数据。
应用程序INPUT数据验证失败不得要求用户交互。INPUT数据验证失败表示“用户发送了 无效值。(=客户端不能/不应发送的值。不可接受值,例如太大、字符编码损坏、格式/字符损坏等)。这些无效输入必须简单地拒绝,并根据 “快速失败” 原则进行处理。即像WAF(Web应用程序防火墙)一样拒绝无效输入。所有输入,包括HTTP头/查询参数,都必须始终进行验证。
输入数据格式的正确性必须由服务器始终进行验证,并且验证必须尽可能快地进行。输入数据验证不应需要用户交互,不应提供对攻击者有意义的错误信息。
业务逻辑数据验证
重要:在业务逻辑代码中检测损坏的/完全错误的/无效的输入太晚了。这对于“安全失败”很好,但“安全失败”不是理想的安全措施。
业务逻辑数据验证是为了检测用户允许的输入错误,而不是来自 crackers 的完全错误的输入。业务逻辑(例如 MVC 中的模型)不应处理完全错误的输入值(例如,期望数字,但提供了字符串),因为完全错误的数据应该已经由应用程序输入验证处理。
应用业务逻辑数据验证,通过业务逻辑验证值。例如,预订日期是未来日期,最小值小于最大值,具有有效的CSRF令牌等。业务逻辑数据验证主要负责逻辑正确性。
与应用输入数据验证不同,许多业务逻辑数据验证需要用户交互来纠正输入错误。
逻辑数据正确性必须由服务器始终进行验证。业务逻辑验证通常需要用户交互,并且应提供有关出错原因的有意义错误信息。
参考
- 输入和业务逻辑验证是两种不同的验证。OWASP代码审查指南 - 第7.6节输入验证,详情请见。
- 输入验证失败不能简单地忽略。2017 OWASP TOP 10 - "A10 缺乏日志和监控"。
- 输入数据验证是最强大的安全措施。CWE/SANS Top 25 - 怪物缓解措施。
- 输入数据验证是安全编码的第一原则。CERT TOP 10 安全编码实践
文档
参考。
示例。
代码。
- validate()和其他函数定义在 validate_func.php
- 验证器行为定义在 validate.php
- 验证器标志定义在 validate_defs.php
状态
- 预 alpha
- 请试用并报告错误。
待办事项
- 添加Unicode验证。例如,RFC 3454,.NET样式的UnicodeCategory(RFC 3454 C检查已实现。白名单可能将在C模块中从Unicode标准字符定义XML中实现。RFC 3454 C检查使字符串验证速度慢6倍。)
- 更多测试。(大多数功能都已测试。)
- 一些小功能 - 浮点数没有像C模块那样进行验证等。
- 在API修复后将其回滚到C模块 - 待办事项:清理、优化、重新组织PHP代码以适应C实现。
- 规范构建器应用(?)。
- 学习和自动规范构建工具(?)。
输入数据安全提示
字符串是最危险的输入,Web应用是由字符串输入构成的。即几乎所有Web应用的输入都是字符串。无效的字符串绝对不能被Web应用的代码处理。
虽然大多数Web应用不验证输入字符串的字符编码,但Web开发人员必须验证字符编码。如果不验证字符编码,您的应用程序很容易受到DoS攻击。即htmlspecialchars()返回空字符串,现代浏览器拒绝渲染严重损坏的编码,系统既有二进制安全的API/存储,也有编码感知的API/存储。这些事实创造了难以发现DoS漏洞。
为了程序能够正确工作,有效的数据是绝对必要的。无效数据会导致程序始终处于无效状态。
一些应用程序/库净化输入数据,将"无效数据"转换为"有效数据",因为"有效数据"是强制性的。然而,净化忽略了并隐藏了来自网络犯罪分子的攻击。忽略并隐藏攻击是不安全的做法,应避免。开发人员通常不应依赖净化器。
扩展
由于"Validate"被设计为框架,因此很容易扩展。它可以与其他验证器(如Respect)一起工作。
“src/tools” 目录包含请求日志、从日志创建验证规则和验证脚本的工具。
“Validate” C 扩展模块
此 PHP 脚本基于 PHP 7 的 validate C 模块。计划将功能迁移到 C 模块,该模块可以更快地执行验证。
https://github.com/yohgaki/validate-php(请不要使用此链接。目前请使用 PHP 脚本版本。)
评论与问题
欢迎评论、错误报告和 PR!请记住,“Validate”未针对面向对象或 PHP 脚本进行优化,而是针对 C 模块。此脚本计划在未来以 C 模块的形式实现。