preg_match(_all)的变量初始化问题

author: 80vul-B
team:http://www.80vul.com
date:2009-04-27

一 描叙

php手册里:
—————————————————————————————–
int preg_match ( string pattern, string subject [, array matches [, int flags]] )

在 subject 字符串中搜索与 pattern 给出的正则表达式相匹配的内容。
如果提供了 matches,则其会被搜索的结果所填充。$matches[0] 将包含与整个模式匹配的文本,$matches[1] 将包含与第一个捕获的括号中的子模式所匹配的文本,以此类推。
—————————————————————————————–

这个是一个应用比较多的函数,很多应用程序忘记对preg_match(_all)的变量进行初始化,导致安全漏洞.

二 分析

patch_match的部分源码:

PHP_FUNCTION(preg_match)
{
	php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
...
// php_pcre.c
static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, int global) /* {{{ */
{
...
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, ((global) ? "ssz|ll" : "ss|zll"), &regex, &regex_len,
							  &subject, &subject_len, &subpats, &flags, &start_offset) == FAILURE) {
		RETURN_FALSE;
	}
// 调用zend_parse_parameters检查传入的参数,参数类型及格式限定为了ss|zll

// zend_API.c
ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...)
{
...
	retval = zend_parse_va_args(num_args, type_spec, &va, 0 TSRMLS_CC);
...
static int zend_parse_va_args(int num_args, char *type_spec, va_list *va, int flags TSRMLS_DC)
{
...
	while (num_args-- > 0) {
		arg = (zval **) p - (arg_count-i);
		if (*type_spec == '|') {
			type_spec++;
		}
		if (zend_parse_arg(i+1, arg, va, &type_spec, quiet TSRMLS_CC) == FAILURE) {
// 调用zend_parse_arg检查每个参数的类型
			return FAILURE;
		}
...
static int zend_parse_arg(int arg_num, zval **arg, va_list *va, char **spec, int quiet TSRMLS_DC)
{
...
	expected_type = zend_parse_arg_impl(arg_num, arg, va, spec TSRMLS_CC);
	if (expected_type) {
		if (!quiet && *expected_type) {
			char *space;
			char *class_name = get_active_class_name(&space TSRMLS_CC);

			zend_error(E_WARNING, "%s%s%s() expects parameter %d to be %s, %s given",
					class_name, space, get_active_function_name(TSRMLS_C), arg_num, expected_type,
					zend_zval_type_name(*arg));
// 如果参数的类型非法,报错并导致zend_parse_parameters返回FAILURE
		}
...
static char *zend_parse_arg_impl(int arg_num, zval **arg, va_list *va, char **spec TSRMLS_DC)
{
	char *spec_walk = *spec;
	char c = *spec_walk++;
...
	switch (c) {
...
		case 's':
			{
				char **p = va_arg(*va, char **);
				int *pl = va_arg(*va, int *);
				switch (Z_TYPE_PP(arg)) {
					case IS_NULL:
						if (return_null) {
							*p = NULL;
							*pl = 0;
							break;
						}
						/* break omitted intentionally */

					case IS_STRING:
					case IS_LONG:
					case IS_DOUBLE:
					case IS_BOOL:
						convert_to_string_ex(arg);
						*p = Z_STRVAL_PP(arg);
						*pl = Z_STRLEN_PP(arg);
						break;

					case IS_OBJECT:
						if (parse_arg_object_to_string(arg, p, pl, IS_STRING TSRMLS_CC) == SUCCESS) {
							break;
						}

					case IS_ARRAY:
					case IS_RESOURCE:
					default:
						return "string";
				}
			}
			break;
...

// 由上面的代码可以看到如果传入preg_match的第一个或者第二个参数是一个数组或者资源类型的话,将会报错并返回false,这将导致如果使用第三个参数的话,该变量将不会被赋值

三 测试代码

<?php
//preg_match.php?ipmatches[]=1111
$ip[]='1';
preg_match("/[\d\.]{7,15}/", $ip, $ipmatches);
var_dump($ipmatches);
?>

四 实际应用

[SODB-2009-01]:Discuz!<5.50 preg_match()的变量$onlineipmatches未初始化漏洞
[http://www.80vul.com/dzvul/]

相关日志

发表评论