PHP中“==”运算符的安全问题

澳门新葡亰娱乐在线 5

前言

PHP是一种通用的开源脚本语言,它的语法混合了C,Java,以及Perl等优秀语言的语法。除此之外,它还提供了大量的函数库可供开发人员使用。但是,如果使用不当,PHP也会给应用程序带来非常大的安全风险。

在这篇文章中,我们将会对PHP应用程序中经常会出现的一些问题进行深入地分析,尤其是当我们使用“==”(比较运算符)来进行字符串比较时,可能会出现的一些安全问题。虽然近期有很多文章都围绕着这一话题进行过一些探讨,但我决定从“黑盒测试”的角度出发,讨论一下如何利用这个问题来对目标进行渗透和攻击。首先,我会对引起这个问题的根本原因进行分析,以便我们能够更加深入地理解其工作机制,这样才可以保证我们能够尽可能地避免这种安全问题的发生。

前言

问题的描述

在2011年,PHP官方漏洞追踪系统发现,当字符串与数字在进行比较的时候,程序会出现某些非常奇怪的现象。从安全的角度出发,这个问题实际上并不能算是一个安全问题。比如说,你可以看到下面这段代码:

澳门新葡亰娱乐在线 1

实际上,当使用类似“==”这样的比较运算符进行操作时,就会出现这样的情况。上面这个例子中出现的问题不能算是一个漏洞,因为它是PHP所提供的一种名为“类型转换”的功能。从本质上来分析,当我们使用特定的比较运算符(例如==
, !=,
<>)来进行操作时,PHP首先会尝试去确定参与比较的数据类型。但是这样的一种类型转换机制将有可能导致计算结果与我们预期的结果有较大出入,而且也会带来非常严重的安全问题。安全研究专家在该问题的完整披露报告中写到:这种类型转化机制将有可能导致权限提升,甚至还会使程序的密码验证过程变得不安全。

Gynvael写过一篇关于这一话题的经典文章,PHP等号运算符“==”所涵盖的数据类型非常广泛,我们给大家提供了一个较为完整的比较参考列表,并给出了一些示例,具体内容如下所示:

澳门新葡亰娱乐在线 2

正如你所看到的,当我们使用“==”来比较这些数字字符串时,参与比较的就是字符串中数字的实际大小,从安全的角度出发,这就是一个非常有趣的问题了。在这种情况下,你可以使用科学计数法来表示一个数字,并将其放在一个字符串中,PHP将会自动把它作为一个数字类型来处理。我们之所以会得到这样的输出类型,是因为PHP使用了一种哈希算法(通常使用十六进制数值表示)来进行处理。比如说,如果一个数字为0,那么在进行松散比较的过程中,PHP会自动对其类型进行转换,但其值永远为0。对于一个给定的散列算法而言,密码就有可能会变成可以被替换的了。比如说,当密码的哈希值被转换成使用科学计数法来表示的数字时,将有可能正好与其他的密码哈希相匹配。这样一来,即使是一个完全不同的密码,也有可能可以通过系统的验证。但有趣的是,当某些采用科学计数法表示的数字在进行比较的时候,结果可能会让你意想不到:

澳门新葡亰娱乐在线 3

在使用PHP开发Web应用的中,很多的应用都会要求用户注册,而注册的时候就需要我们对用户的信息进行处理了,最常见的莫过于就是邮箱和密码了,本文意在讨论对密码的处理:也就是对密码的加密处理。

从“黑盒测试”的角度出发来考虑这个问题

从静态分析的角度来看,这些安全问题就显得有些普通了。但如果我们从黑盒的角度来看待这些问题,我们能够得到什么样的启发呢?对于应用程序中的任何用户账号而言,如果应用程序使用了当前最为流行的哈希散列算法(例如SHA1和MD5)来对密码进行处理,而你在对密码哈希进行验证的时候使用了PHP的松散比较,那么此时就有可能出现安全问题。我们现在可以考虑进行一次典型的渗透测试,你可以创建一个普通的账号,将密码设置成哈希值类似的其中一个密码,然后使用其他的密码进行登录操作。很明显,系统的安全性完全取决于你所使用的散列算法。所以,我们假设你没有在散列算法中使用“Salt”值,那么你至少得使用两种不同的散列算法来对密码进行处理。

现在,在我们去对这些密码组合进行研究之前,我们还应该考虑到一点——即密码的要求。因为我们在对这些密码和散列算法进行分析之前,首先得确保我们所设置的初始密码复合了密码复杂度的要求,否则我们的分析和研究将会没有任何的意义。因此,我们得确保我们的密码长度至少为八个字符,密码中包含有大小写字母,数字,以及至少一个特殊字符:具体如下所示:

import random
import hashlib
import re
import string
import sys
prof = re.compile("^0+ed*$") # you can also consider: re.compile("^d*e0+$")
prefix = string.lower(sys.argv[1])+'!'+string.upper(sys.argv[1])+"%s"
num=0
while True:
    num+=1
    b = hashlib.sha256(prefix % num).hexdigest()
    if (b[0]=='0' and prof.match(b)):
        print(prefix+str(num),b)

为此,我专门编写了一个Python脚本,虽然我没有竭尽全力去优化这个脚本的性能,但是在PyPy编译器的帮助下,这个精心编写的脚本可以在我的AMD
FX8350所有可用的CPU核心中稳定运行。除此之外,我还使用到了hashlib库中的散列函数,而且为了避免遇到Python
GIL的进程同步问题,我还生成了独立的进程来对密码数据进行处理。不仅如此,我还使用了非常复杂的技术来为每一个密码生成不同的前缀,正如上面这段代码所示。

密码安全的重要性我们就不用再去强调,随着在线攻击的增多,如果我们对密码没有进行合适的处理或做防御措施,我们的应用就会肯定会收到来自各方的威胁和攻击。

分析结果

在经过了一个多小时的分析之后,我得到了四个密码的SHA1值。令我感到惊讶的是,得到四个密码的MD5值所需的时间竟然更短。

密码的计算结果十分相似,具体如下所示:

澳门新葡亰娱乐在线 4

你可以随意选取两个密码来进行对比,对比的演示结果如下:

澳门新葡亰娱乐在线 5

如果你无法得到如上图所示的计算结果,那么你应该感到幸运。你可以尝试将用户名和密码捆绑在一起,然后使用带“salt”值的散列算法来进行计算。你只需要修改一小部分代码即可实现,点击“这里”获取修改后的脚本。

所以作为开发者,我们需要对用户的密码做好预防措施。

解决方案

PHP给我们提供了一个解决方案,如果你想要对比哈希值,你应该使用password_verify()或hash_equals()这两个函数。它们会对数据进行严格比较,并排除一些其他的干扰因素。但是请你注意,hash_equals()函数也可以用于字符串的比较。

关于密码我们应该遵守的一些原则

分析结论

虽然我们的分析步骤执行起来有些过于复杂,但是从黑盒测试的角度出发,我们所描述的方法也许可以给大家提供一些有价值的信息。如果某个应用程序中的密码采用了这样的一种验证机制,那么它所带来的安全问题将会超出PHP数据类型转换本身所存在的问题。

绝对不能知道用户的密码

问题远不止于此

这个问题给我们带来的影响远远不止于此。攻击者可以将这些密码添加到字典文件中,然后对应用程序中的所有用户进行暴力破解攻击。而且,如果应用程序的密码恢复机制中存在不安全的因素,攻击者还有可能对目标账号进行不限次数的攻击,直到攻击成功为止。

我们绝对不能知道用户的密码,也不能有获取用户密码的方式。
知道的越少越安全。

源代码

点击“这里”获取Python源码。

绝对不去约束用户的密码

最好不要去约束密码的长度、格式等。
如果要求密码符合一个特定的模式,其实对于那些不怀好意的人也提供了攻击的途径。
如果必须要约束的话,建议只限制最小长度。并把常用的密码或基于字典创建的密码加入黑名单,也是一个好主意。

绝对不通过电子邮件发送用户的密码

对于一个web应用来说,重置或修改密码时,我们应该在邮件里发送用于设定或修改密码的
URL
。而且这个URL中应该会包含一个唯一的令牌,这个令牌只能在设定或修改密码时使用一次。在设定或修改密码之后,我们就应该把这个令牌置为失效。

使用 bcrypt 计算用户密码的哈希值

目前,通过大量的审查,最安全的哈希算法是 bcrypt 。

首先,我们明确两个概念,哈希、加密。哈希和加密有什么区别?

加密

加密是双向算法,加密的数据之后通过解密还可以得到。

澳门新葡亰娱乐在线,哈希

哈希是单向算法,哈希后的数据不能再还原成原始值。

用户提高密码等需要单向验证的数据的安全性
一般我们在数据库中保存的应该是计算出来的密码的哈希值。这样即使我们的数据库泄露了,他们也只能看到这些无意义的密码的哈希值。

MD5

MD5即Message-Digest Algorithm
5,用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一,主流编程语言普遍已有MD5实现。将数据运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。

SHA1

安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准 (Digital
Signature Standard DSS)里面定义的数字签名算法(Digital Signature
Algorithm
DSA)。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。
SHA1有如下特性:不可以从消息摘要中复原信息;两个不同的消息不会产生同样的消息摘要,(但会有1×10
^ 48分之一的机率出现相同的消息摘要,一般使用时忽略)。

bcrypt

bcrypt是专门为密码存储而设计的算法,基于Blowfish加密算法变形而来,由Niels
Provos和David Mazières发表于1999年的USENIX。
bcrypt最大的好处是有一个参数,可用于调整计算强度,而且work
factor是包括在输出的摘要中的。随着攻击者计算能力的提高,使用者可以逐步增大work
factor,而且不会影响已有用户的登陆。
bcrypt经过了很多安全专家的仔细分析,使用在以安全着称的OpenBSD中,一般认为它比PBKDF2更能承受随着计算能力加强而带来的风险。bcrypt也有广泛的函数库支持,因此我们建议使用这种方式存储密码。

scrypt

scrypt不仅计算所需时间长,而且占用的内存也多,使得并行计算多个摘要异常困难,因此利用rainbow
table进行暴力攻击更加困难。scrypt没有在生产环境中大规模应用,并且缺乏仔细的审察和广泛的函数库支持
。但是,scrypt在算法层面只要没有破绽,它的安全性应该高于PBKDF2和bcrypt。

目前,通过大量的审查,最安全的哈希算法是 bcrypt 。与 MD5 和 SHA1 不同,
bcrypt 算法会自动加盐,来防止潜在的彩虹表攻击。 bcrypt
算法会花费大量的时间反复处理数据,来生成安全的哈希值。在这个过程中,处理数据的次数叫工作因子。工作因子的值越高,破解密码哈希值的时间会成指数倍增长。

bcrypt
算法永不过时,如果计算机的运算速度变快了,我们只需要提高工作因子即可。

顺带说一下,任何情况下尽可能的不要使用 md5 算法,至少也要使用 SHA
系列的哈希算法。因为md5算法以目前计算机的计算能力来说显得比较简单,而
md5 的性能优势现在也已经完全可以忽略不计了。

密码哈希API

上面我们说到 bcrypt 算法最安全,最适合对我们的密码进行哈希。 PHP 在
PHP5.5.0+
的版本中提供了原生的密码哈希API供我们使用,这个密码哈希API默认使用的就是
bcrypt 哈希算法,从而大大简化了我们计算密码哈希值和验证密码的操作。

PHP原生密码哈希API

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章

网站地图xml地图