验证不受信任的输入:数字

安全地验证不受信任的输入对于应用安全至关重要:SQL注入、XSS和恶意文件上传都是常见的攻击,因为用户的输入没有正确验证。

数字是问题所在:负数(“销售价格是-500美元”)、非常大的数字(“我的账户余额是9,223,372,036,854,775,807”)或非数字(“rm -rf /”)如果没有小心处理,都可能造成破坏。

幸运的是,Perl具有强大的验证输入的能力,但有一些边缘情况需要注意,使得回答“$x 是否是数字?”比您想象的要困难。

模式匹配

当然,问题的一部分是,数字的种类比我们通常认为的要多。正则表达式非常适合像十进制整数验证这样的常见情况:例如,/^\d+$/可以确认输入只包含数字。这可能对您的应用程序足够了,但请注意,它不能处理所有整数的排列。如果您想接受负数怎么办?

您可以更新正则表达式以接受可选的减号:/^-?\d+$/或使用来自Regexp::Common::number的标准正则表达式,它还有匹配小数位、千位分隔符和其他常见但难以匹配的事物的模式。

大整数可能也无法与\d匹配。Perl有三种不同的方式来存储数字:作为本地C整数、作为8字节的浮点数或作为以“e”表示法表示的十进制字符串(见perlnumber)。在我的机器上,Perl将123456789012345678905存储为十进制字符串1.23456789012346e+20,这与只包含整数的正则表达式不匹配。8字节浮点数和十进制字符串是不精确的,因此如果您需要接受大于您的机器架构(32位或64位)的整数,您应该使用像Math::BigInt这样的模块。

如果您启用了Perl的污染模式,正则表达式捕获是“去污染”输入的正确方式,在这种情况下,您别无选择,只能使用它们。

看起来像数字

使用正则表达式的一种补充技术是使用来自Scalar::Util的函数looks_like_number。这是一个布尔函数,如果变量对Perl解释器看起来像数字,则返回true。

与简单的正则表达式不同,它可以很好地识别负数和十进制字符串,但它有自己的怪癖,您应该了解。例如,所有这些字符串“看起来像数字”

NaN
-nan
inf
infinity
-infinity

糟糕!

looks_like_number的另一个怪癖存在于Scalar::Util的较旧版本中(直到v1.38,与Perl 5.20一起发布):它的返回值取决于正在检查的变量的值

$ perl -MScalar::Util=looks_like_number -e 'print looks_like_number($_), "\n" for (1,"5","5e60")'
16842752
1
4

这是因为looks_like_number正在返回Perl解释器的C函数返回值,这可能会包括几个不同的标志的组合,Perl为每个变量保留这些标志(stackoverflow)。

所有这些都是真值,所以如果您没有编写期望返回值为1的条件,这不应该成为问题

use Scalar::Util 'looks_like_number';

# wrong
if (looks_like_number($foo) == 1) ...

# right!
if (looks_like_number($foo)) ...

观察者效应

Perl的另一个边缘情况是,观察标量的值可能会将标量的类型从数字更改为字符串。

Perl标量可以包含不同类型的数据,如字符串、整数和浮点数。这通常很方便:如果您需要打印一个数字,您不必先将其转换为字符串,因为Perl会尽量“做正确的事”。为了效率,Perl解释器将数字转换为字符串并存储在标量的字符串槽中,因此如果标量被插值第二次,Perl就不需要再次将其转换为字符串。

这种问题的一个常见表现是在将Perl数据结构序列化为JSON时。当标量包含数字并转换为字符串时,它们会被序列化为JSON字符串,而不是整数。

$ perl -MJSON -E 'my $n = 1; say encode_json([$n]); say "$n"; say encode_json([$n])'
[1]
1
["1"]

在字符串中插值一个数字或与正则表达式匹配都会导致数字转换为字符串。根据您的需求,这可能无关紧要,但如果这很重要,当验证数字输入时,首先对变量做一个本地副本,以便您的验证程序不会微妙地更改变量类型。

结合技术

将这些想法结合到一个子程序中

use Scalar::Util 'looks_like_number';

sub is_number {
  my $num = shift;
  return looks_like_number($num) && $num !~ /inf|nan/i;
}

我定义了名为is_number的子程序,它是一个布尔函数,接受一个值并返回一个布尔值,表示该值对于Perl来说看起来像一个数字,并且不是无穷大或非数字。它复制变量而不更改其类型。这将适用于广泛范围的数字类型,包括Perl转换为十进制字符串的真正大的数字(效果可疑)!

您的应用程序的需求决定了您应该接受哪些类型的数字,但请记住,您接受的数字类型越多,验证就越复杂。但是,如果您熟悉这些边缘情况,任务就会变得容易一些。

标签

大卫·费尔兰(David Farrell)

大卫是一名职业程序员,他经常在推文博客中讨论代码和编程的艺术。

浏览他们的文章

反馈

这篇文章有什么问题吗?请在GitHub上打开一个问题或拉取请求,以帮助我们。