PHP substr_compare() Vulnerability 浅析
Author: wofeiwo
Date: Aug 14th 2006
先看看公告:
PHP多个远程安全漏洞
发布日期:2006-08-03
更新日期:2006-08-04
受影响系统:
PHP PHP 4.4.x
不受影响系统:
PHP PHP 4.4.3
描述:
--------------------------------------------------------------------------------
CVE(CAN) ID: CVE-2006-3016
PHP是广泛使用的通用目的脚本语言,特别适合于Web开发,可嵌入到HTML中。
PHP的substr_compare()函数没有正确的验证偏移/长度参数。此外,PHP还没有正确的处理会话名称中的某些字符。攻击者可以利用这些漏洞远程执行任意代码。
<*来源:Secunia
链接:http://secunia.com/advisories/21328/print/
http://www.php.net/release_4_4_3.php
*>
公告里只说明影响为 PHP <= 4.4.3, 其实 PHP 5.1.3 以下也受到这个漏洞影响.
再来看看 PHP 手册里对这个函数的描述.
Description
int
substr_compare
(
string
main_str
,
string
str
,
int offset [
,
int length [
,
bool case_insensitivity]] )
substr_compare() compares main_str from position offset with str up to length characters.
Returns < 0 if main_str from position offset is less than str, > 0 if it is greater than str, and 0 if they are equal. If length is equal or greater than length of main_str and length is set, substr_compare() prints warning and returns FALSE.
If case_insensitivity is TRUE, comparison is case insensitive
看来是一个比较字符串与子字符串的函数.下面来分析漏洞原因,看代码:
//
ripped from PHP-5.1.2
PHP_FUNCTION(
substr_compare
)
{
char
*
s1
,
*
s2;
int s1_len
,
s2_len;
long offset
,
len
=
0
;
zend_bool cs
=
0
;
uint cmp_len;
if
(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC
,
"
ssl|lb
"
,
&
s1
,
&
s1_len
,
&
s2
,
&
s2_len
,
&
offset
,
&
len
,
&
cs)
==
FAILURE) {
RETURN_FALSE;
}
if
(len
&&
offset
>=
s1_len) {
//
简单的检查了 len 是否 != 0 并且 offset 大于 strlen(main_str). 但是 len 和 offset 都可以为负数
php_error_docref(
NULL
TSRMLS_CC
,
E_WARNING
,
"
The start position cannot exceed initial string length.
"
);
RETURN_FALSE;
}
if
(offset
<
0
) {
//
offset 为负数,则从 main_str 的尾向前偏移
offset
=
s1_len
+
offset;
//
这里出现问题了.如果 offset 是负数,且绝对值大于 s1_len 呢?得到的 offset 仍然为负数
}
cmp_len
=
(uint) (len
?
len
:
MAX
(s2_len
,
(s1_len
-
offset)));
//
这里确定比较的长度,很容易被控制.
if
(
!
cs) {
//
传递参数给 zend_binary_strncmp 或 zend_binary_strncasecmp
RETURN_LONG(zend_binary_strncmp(s1
+
offset
,
(s1_len
-
offset)
,
s2
,
s2_len
,
cmp_len));
}
else
{
RETURN_LONG(zend_binary_strncasecmp(s1
+
offset
,
(s1_len
-
offset)
,
s2
,
s2_len
,
cmp_len));
}
}
再来看 zend_binary_strncmp 函数:
ZEND_API int zend_binary_strncmp(char
*
s1
,
uint len1
,
char
*
s2
,
uint len2
,
uint length)
{
int retval;
retval
=
memcmp(s1
,
s2
,
MIN
(length
,
MIN
(len1
,
len2)));
if
(
!
retval) {
return
(
MIN
(length
,
len1)
-
MIN
(length
,
len2));
}
else
{
return
retval;
}
}
很明显,如果提交我们设计的参数,很容易造成crash.
下面是 PHP-5.1.4 修补后的代码
//
ripped from PHP-5.1.4
PHP_FUNCTION(
substr_compare
)
{
char
*
s1
,
*
s2;
int s1_len
,
s2_len;
long offset
,
len
=
0
;
zend_bool cs
=
0
;
uint cmp_len;
if
(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC
,
"
ssl|lb
"
,
&
s1
,
&
s1_len
,
&
s2
,
&
s2_len
,
&
offset
,
&
len
,
&
cs)
==
FAILURE) {
RETURN_FALSE;
}
if
(ZEND_NUM_ARGS()
>=
4
&&
len
<=
0
) {
//
len不能为负数了
php_error_docref(
NULL
TSRMLS_CC
,
E_WARNING
,
"
The length must be greater than zero
"
);
RETURN_FALSE;
}
if
(offset
<
0
) {
offset
=
s1_len
+
offset;
offset
=
(offset
<
0
)
?
0
:
offset;
//
检查是否 offset 仍然为负数,是,则设为0
}
if
((offset
+
len)
>=
s1_len) {
//
offset+len 也不能大于 s1_len
php_error_docref(
NULL
TSRMLS_CC
,
E_WARNING
,
"
The start position cannot exceed initial string length
"
);
RETURN_FALSE;
}
cmp_len
=
(uint) (len
?
len
:
MAX
(s2_len
,
(s1_len
-
offset)));
if
(
!
cs) {
RETURN_LONG(zend_binary_strncmp(s1
+
offset
,
(s1_len
-
offset)
,
s2
,
s2_len
,
cmp_len));
}
else
{
RETURN_LONG(zend_binary_strncasecmp(s1
+
offset
,
(s1_len
-
offset)
,
s2
,
s2_len
,
cmp_len));
}
}