不可信的数据来自许多来源(用户、第三方站点,甚至您自己的数据库!),在使用前必须对所有数据进行检查。
请记住:即使是管理员也是用户,用户可能会故意或意外地输入错误数据。保护他们免受自身错误的侵害是您的职责。
验证输入是指将数据与预定义的模式(或多个模式)进行测试,并得出明确的结果:有效或无效。与清理相比,验证是一种更具体的方法,但两者都有其作用。
简单的验证示例:
- 检查必填字段是否未被留空
- 检查输入的电话号码仅包含数字和标点符号
- 检查请求的字符串是否为五个有效选项之一
- 检查数量字段是否大于 0
数据验证应尽早进行。这意味着在执行任何操作之前先验证数据。
验证理念
关于如何进行验证有几种不同的理念。每种理念都适用于不同的场景。
白名单(Safelist)
仅接受来自有限已知且可信值的列表的数据。
当将不可信数据与白名单进行比较时,重要的是要确保使用严格的类型检查。否则,攻击者可以构造输入以通过白名单检查但仍产生恶意效果。
比较运算符
$untrusted_input = '1 malicious string'; // will evaluate to integer 1 during loose comparisons
if ( 1 === $untrusted_input ) { // == would have evaluated to true, but === evaluates to false
echo '<p>Valid data';
} else {
wp_die( 'Invalid data' );
}in_array()
$untrusted_input = '1 malicious string'; // will evaluate to integer 1 during loose comparisons
$safe_values = array( 1, 5, 7 );
if ( in_array( $untrusted_input, $safe_values, true ) ) { // `true` enables strict type checking
echo '<p>Valid data';
} else {
wp_die( 'Invalid data' );
}switch()
$untrusted_input = '1 malicious string'; // will evaluate to integer 1 during loose comparisons
switch ( true ) {
case 1 === $untrusted_input: // do your own strict comparison instead of relying on switch()'s loose comparison
echo '<p>Valid data';
break;
default:
wp_die( 'Invalid data' );
}黑名单(Blocklist)
拒绝来自有限已知不可信值列表的数据。这通常不是一个好主意。
格式检测
测试数据是否具有正确的格式。只有符合时才接受。
if ( ! ctype_alnum( $data ) ) {
wp_die( "Invalid format" );
}
if ( preg_match( "/[^0-9.-]/", $data ) ) {
wp_die( "Invalid format" );
}格式修正
接受几乎所有数据,但移除或更改危险部分。
$trusted_integer = (int) $untrusted_integer;
$trusted_alpha = preg_replace( '/[^a-z]/i', "", $untrusted_alpha );
$trusted_slug = sanitize_title( $untrusted_slug );示例一
假设我们有一个旨在接受美国邮政编码的输入字段:
<input type="text" id="wporg_zip_code" name="my-zipcode" maxlength="10" />在这里,我们告诉浏览器只允许最多十个字符的输入……但对于他们可以输入哪些字符没有限制。他们可以输入 11221 或 eval()。
这就是验证发挥作用的地方。在处理表单时,我们编写代码来检查每个字段的数据类型是否正确,如果不正确则丢弃它。
例如:为了检查 my-zipcode 字段,我们可以这样做:
/**
* Validate a US zip code.
*
* @param string $zip_code RAW zip code to check.
*
* @return bool true if valid, false otherwise.
*/
function wporg_is_valid_us_zip_code( string $zip_code ):bool {
// Scenario 1: empty.
if ( empty( $zip_code ) ) {
return false;
}
// Scenario 2: more than 10 characters.
// The `maxlength` attribute is only enforced by
// the browser, so we still need to validate the
// length of the input on the server to protect
// against a manual submission.
if ( 10 < strlen( trim( $zip_code ) ) ) {
return false;
}
// Scenario 3: incorrect format.
if ( ! preg_match( '/^d{5}(-?d{4})?$/', $zip_code ) ) {
return false;
}
// Passed successfully.
return true;
}然后,在处理表单时,您的代码应检查 wporg_zip_code 字段并根据结果执行操作:
if ( isset( $_POST['wporg_zip_code'] ) && wporg_is_valid_us_zip_code( $_POST['wporg_zip_code'] ) ) {
// $_POST['wporg_zip_code'] is valid; carry on
}请注意,此特定示例只是检查提供的数据格式是否正确;它不检查提供的且格式正确的数据是否是有效的邮政编码。为此,您需要第二个函数将其与有效邮政编码列表进行比较。
示例二
假设您的代码将查询数据库中的帖子,并且您希望允许用户对查询结果进行排序。
$allowed_keys = array( 'author', 'post_author', 'date', 'post_date' );
$orderby = sanitize_key( $_POST['orderby'] );
if ( in_array( $orderby, $allowed_keys, true ) ) {
// $orderby is valid; carry on
}此示例代码通过将传入的排序键(存储在 orderby 输入参数中)与允许的排序键数组进行比较来检查其有效性。这防止了用户传入任意且可能恶意的数据。
在将传入的排序键与数组进行比较之前,该键被传递给内置的 WordPress 函数 sanitize_key()。该函数确保(除其他外)键为小写,这是我们想要的,因为 in_array() 执行区分大小写的搜索。
向 in_array() 的第三个参数传递 true 可启用严格的类型检查,这会告诉函数不仅比较值,还要比较值的类型。这使得代码能够确定传入的排序键是字符串而不是其他数据类型。
验证函数
大多数验证是作为自定义代码的一部分完成的,但也有一些辅助函数可用。这些函数除了清理页面列出的函数之外还有更多。
balanceTags( $html )或force_balance_tags( $html )– 尝试确保 HTML 标签平衡,从而输出有效的 XML。count()用于检查数组中有多少项in_array()用于检查某物是否存在于数组中is_email()将验证电子邮件地址是否有效。is_array()用于检查某物是否为数组mb_strlen()或strlen()用于检查字符串是否具有预期的字符数preg_match(),strpos()用于检查其他字符串中是否出现某些字符串sanitize_html_class( $class, $fallback )– 清理 HTML 类名以确保其仅包含有效字符。将字符串缩减为 A-Z,a-z,0-9,'-',如果 resulting 结果为空字符串,则返回提供的备用值。tag_escape( $html_tag_name )– 清理 HTML 标签名称(尽管函数名称如此,但不会转义任何内容)。term_exists()检查标签、类别或其他分类法术语是否存在。username_exists()检查用户名是否存在。validate_file()将验证输入的文件路径是否为真实路径(但不检查文件是否存在)。
查看 WordPress 代码参考 以获取更多此类函数。搜索名称类似于以下内容的函数:*_exists(),*_validate() 和 is_*()。并非所有这些函数都是验证函数,但其中许多很有帮助。