yohgaki/validate-php-scr

简单的通用输入数据验证框架。脚本版本。

v0.8.0 2022-10-08 10:37 UTC

This package is auto-updated.

Last update: 2024-09-08 15:55:55 UTC


README

Build Status

"Validate"是一个旨在为“CERT安全编码”和“设计由合同”(DbC)有用的“输入数据验证框架”。“Validate”旨在开发符合OWASP TOP 10 A10:2017漏洞的应用程序。“Validate”计划作为PHP的C模块实现。

CERT Top 10 安全编码实践。

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令牌等。业务逻辑数据验证主要负责逻辑正确性。

与应用输入数据验证不同,许多业务逻辑数据验证需要用户交互来纠正输入错误。

逻辑数据正确性必须由服务器始终进行验证。业务逻辑验证通常需要用户交互,并且应提供有关出错原因的有意义错误信息。

参考

文档

参考。

示例。

代码。

状态

  • 预 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 模块的形式实现。