当前位置: 首页 > 编程日记 > 正文

PHP哈希表碰撞攻击原理

哈希表碰撞攻击(Hashtable collisions as DOS attack)的话题不断被提起,各种语言纷纷中招。本文结合PHP内核源码,聊一聊这种攻击的原理及实现。

哈希表碰撞攻击的基本原理

哈希表是一种查找效率极高的数据结构,很多语言都在内部实现了哈希表。PHP中的哈希表是一种极为重要的数据结构,不但用于表示Array数据类型,还在Zend虚拟机内部用于存储上下文环境信息(执行上下文的变量及函数均使用哈希表结构存储)。

理想情况下哈希表插入和查找操作的时间复杂度均为O(1),任何一个数据项可以在一个与哈希表长度无关的时间内计算出一个哈希值(key),然后在常量时间内定位到一个桶(术语bucket,表示哈希表中的一个位置)。当然这是理想情况下,因为任何哈希表的长度都是有限的,所以一定存在不同的数据项具有相同哈希值的情况,此时不同数据项被定为到同一个桶,称为碰撞(collision)。哈希表的实现需要解决碰撞问题,碰撞解决大体有两种思路,第一种是根据某种原则将被碰撞数据定为到其它桶,例如线性探测——如果数据在插入时发生了碰撞,则顺序查找这个桶后面的桶,将其放入第一个没有被使用的桶;第二种策略是每个桶不是一个只能容纳单个数据项的位置,而是一个可容纳多个数据的数据结构(例如链表或红黑树),所有碰撞的数据以某种数据结构的形式组织起来。

不论使用了哪种碰撞解决策略,都导致插入和查找操作的时间复杂度不再是O(1)。以查找为例,不能通过key定位到桶就结束,必须还要比较原始key(即未做哈希之前的key)是否相等,如果不相等,则要使用与插入相同的算法继续查找,直到找到匹配的值或确认数据不在哈希表中。

PHP是使用单链表存储碰撞的数据,因此实际上PHP哈希表的平均查找复杂度为O(L),其中L为桶链表的平均长度;而最坏复杂度为O(N),此时所有数据全部碰撞,哈希表退化成单链表。下图PHP中正常哈希表和退化哈希表的示意图。

哈希表碰撞攻击就是通过精心构造数据,使得所有数据全部碰撞,人为将哈希表变成一个退化的单链表,此时哈希表各种操作的时间均提升了一个数量级,因此会消耗大量CPU资源,导致系统无法快速响应请求,从而达到拒绝服务攻击(DoS)的目的。

可以看到,进行哈希碰撞攻击的前提是哈希算法特别容易找出碰撞,如果是MD5或者SHA1那基本就没戏了,幸运的是(也可以说不幸的是)大多数编程语言使用的哈希算法都十分简单(这是为了效率考虑),因此可以不费吹灰之力之力构造出攻击数据。下一节将通过分析Zend相关内核代码,找出攻击哈希表碰撞攻击PHP的方法。

Zend哈希表的内部实现

数据结构

PHP中使用一个叫Backet的结构体表示桶,同一哈希值的所有桶被组织为一个单链表。哈希表使用HashTable结构体表示。相关源码在zend/Zend_hash.h下:

typedef struct bucket {
ulong h;                        /* Used for numeric indexing */
uint nKeyLength;
void *pData;
void *pDataPtr;
struct bucket *pListNext;
struct bucket *pListLast;
struct bucket *pNext;
struct bucket *pLast;
char arKey[1]; /* Must be last element */
} Bucket;
typedef struct _hashtable {
uint nTableSize;
uint nTableMask;
uint nNumOfElements;
ulong nNextFreeElement;
Bucket *pInternalPointer;   /* Used for element traversal */
Bucket *pListHead;
Bucket *pListTail;
Bucket **arBuckets;
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount;
zend_bool bApplyProtection;
#if ZEND_DEBUG
int inconsistent;
#endif
} HashTable; 

字段名很清楚的表明其用途,因此不做过多解释。重点明确下面几个字段:Bucket中的“h”用于存储原始key;HashTable中的nTableMask是一个掩码,一般被设为nTableSize – 1,与哈希算法有密切关系,后面讨论哈希算法时会详述;arBuckets指向一个指针数组,其中每个元素是一个指向Bucket链表的头指针。

哈希算法

PHP哈希表最小容量是8(2^3),最大容量是0×80000000(2^31),并向2的整数次幂圆整(即长度会自动扩展为2的整数次幂,如13个元素的哈希表长度为16;100个元素的哈希表长度为128)。nTableMask被初始化为哈希表长度(圆整后)减1。具体代码在zend/Zend_hash.c的_zend_hash_init函数中,这里截取与本文相关的部分并加上少量注释。

ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC)
{
uint i = 3;
Bucket **tmp;
SET_INCONSISTENT(HT_OK);
//长度向2的整数次幂圆整
if (nSize >= 0x80000000) {
/* prevent overflow */
ht->nTableSize = 0x80000000;
} else {
while ((1U << i) < nSize) {
i++;
}
ht->nTableSize = 1 << i;
}
ht->nTableMask = ht->nTableSize - 1;
/*此处省略若干代码…*/
return SUCCESS;
} 

值得一提的是PHP向2的整数次幂取圆整方法非常巧妙,可以背下来在需要的时候使用。

Zend HashTable的哈希算法异常简单:

即简单将数据的原始key与HashTable的nTableMask进行按位与即可。

如果原始key为字符串,则首先使用Times33算法将字符串转为整形再与nTableMask按位与。

下面是Zend源码中查找哈希表的代码:

ZEND_API int zend_hash_index_find(const HashTable *ht, ulong h, void **pData)
{
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
nIndex = h & ht->nTableMask;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h) && (p->nKeyLength == 0)) {
*pData = p->pData;
return SUCCESS;
}
p = p->pNext;
}
return FAILURE;
}
ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData)
{
ulong h;
uint nIndex;
Bucket *p;
IS_CONSISTENT(ht);
h = zend_inline_hash_func(arKey, nKeyLength);
nIndex = h & ht->nTableMask;
p = ht->arBuckets[nIndex];
while (p != NULL) {
if ((p->h == h) && (p->nKeyLength == nKeyLength)) {
if (!memcmp(p->arKey, arKey, nKeyLength)) {
*pData = p->pData;
return SUCCESS;
}
}
p = p->pNext;
}
return FAILURE;
}

其中zend_hash_index_find用于查找整数key的情况,zend_hash_find用于查找字符串key。逻辑基本一致,只是字符串key会通过zend_inline_hash_func转为整数key,zend_inline_hash_func封装了times33算法,具体代码就不贴出了。

攻击

基本攻击

知道了PHP内部哈希表的算法,就可以利用其原理构造用于攻击的数据。一种最简单的方法是利用掩码规律制造碰撞。上文提到Zend HashTable的长度nTableSize会被圆整为2的整数次幂,假设我们构造一个2^16的哈希表,则nTableSize的二进制表示为:1 0000 0000 0000 0000,而nTableMask = nTableSize – 1为:0 1111 1111 1111 1111。接下来,可以以0为初始值,以2^16为步长,制造足够多的数据,可以得到如下推测:

0000 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0

0001 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0

0010 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0

0011 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0

0100 0000 0000 0000 0000 & 0 1111 1111 1111 1111 = 0

……

概况来说只要保证后16位均为0,则与掩码位于后得到的哈希值全部碰撞在位置0。

下面是利用这个原理写的一段攻击代码:

<?php
$size = pow(2, 16);
$startTime = microtime(true);
$array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
$array[$key] = 0;
}
$endTime = microtime(true);
echo $endTime - $startTime, ' seconds', "\n"; 

这段代码在我的VPS上(单CPU,512M内存)上用了近88秒才完成,并且在此期间CPU资源几乎被用尽:

而普通的同样大小的哈希表插入仅用时0.036秒:

<?php
$size = pow(2, 16);
$startTime = microtime(true);
$array = array();
for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $size; $key += 1) {
$array[$key] = 0;
}
$endTime = microtime(true);
echo $endTime - $startTime, ' seconds', "\n"; 


可以证明第二段代码插入N个元素的时间在O(N)水平,而第一段攻击代码则需O(N^2)的时间去插入N个元素。

POST攻击

当然,一般情况下很难遇到攻击者可以直接修改PHP代码的情况,但是攻击者仍可以通过一些方法间接构造哈希表来进行攻击。例如PHP会将接收到的HTTP POST请求中的数据构造为$_POST,而这是一个Array,内部就是通过Zend HashTable表示,因此攻击者只要构造一个含有大量碰撞key的post请求,就可以达到攻击的目的。具体做法不再演示。

防护

POST攻击的防护

针对POST方式的哈希碰撞攻击,目前PHP的防护措施是控制POST数据的数量。在>=PHP5.3.9的版本中增加了一个配置项max_input_vars,用于标识一次http请求最大接收的参数个数,默认为1000。因此PHP5.3.x的用户可以通过升级至5.3.9来避免哈希碰撞攻击。5.2.x的用户可以使用这个patch:http://www.laruence.com/2011/12/30/2440.html。

另外的防护方法是在Web服务器层面进行处理,例如限制http请求body的大小和参数的数量等,这个是现在用的最多的临时处理方案。具体做法与不同Web服务器相关,不再详述。

其它防护

上面的防护方法只是限制POST数据的数量,而不能彻底解决这个问题。例如,如果某个POST字段是一个json数据类型,会被PHP json_decode,那么只要构造一个超大的json攻击数据照样可以达到攻击目的。理论上,只要PHP代码中某处构造Array的数据依赖于外部输入,则都可能造成这个问题,因此彻底的解决方案要从Zend底层HashTable的实现动手。一般来说有两种方式,一是限制每个桶链表的最长长度;二是使用其它数据结构如红黑树取代链表组织碰撞哈希(并不解决哈希碰撞,只是减轻攻击影响,将N个数据的操作时间从O(N^2)降至O(NlogN),代价是普通情况下接近O(1)的操作均变为O(logN))。

目前使用最多的仍然是POST数据攻击,因此建议生产环境的PHP均进行升级或打补丁。至于从数据结构层面修复这个问题,目前还没有任何方面的消息。

参考

[1] Supercolliding a PHP array

[2] PHP5.2.*防止Hash冲突拒绝服务攻击的Patch

[3] 通过构造Hash冲突实现各种语言的拒绝服务攻击

[4] PHP数组的Hash冲突实例

[5] PHP 5.4.0 RC4 released

相关文章:

Java8(jdk1.8)中文档注释处理工具javadoc的环境参量配置及使用方法

Java8(jdk1.8)中文档注释处理工具javadoc的环境参量配置及使用方法Java语言提供了一种功能强大的注释形式&#xff1a;文档注释。如果编写Java源代码时添加了合适的文档注释&#xff0c;然后通过JDK提供的javadoc工具可以直接将源代码里的文档注释提取成一份系统的API文档。jav…

如何读取Excel表格中不同sheet表的同一位置单元格数据,并绘制条形图呢?

作者 | 黄伟呢来源 | 数据分析与统计学之美今天&#xff0c;有位朋友在群里面咨询了一个问题&#xff1a;如何读取Excel表格中"不同sheet表"的同一位置单元格数据&#xff0c;并绘制条形图呢&#xff1f;有人提议用vba&#xff0c;但是不得不说&#xff0c;没有学过v…

vue-router学习笔记

配置路由模式 const routernew VueRouter({routes }) hash模式(默认):通过url的hash来模拟一个完整的url&#xff0c;于是当url改变时&#xff0c;页面不会重新加载。history模式&#xff1a;通过history完成url跳转而不需要重新加载页面。注意&#xff1a;为了防止404错误&…

PHP防止注入攻击

注入攻击不多说了PHP addslashes() 函数--单撇号加斜线转义PHP String 函数定义和用法addslashes() 函数在指定的预定义字符前添加反斜杠。这些预定义字符是&#xff1a;单引号 ()双引号 (")反斜杠 (\)NULL语法addslashes(string)参数描述string必需。规定要检查的字符串。…

首届腾讯数字安全创新大赛在京启动,挖掘新锐力量推动产业创新

3月10日&#xff0c;首届腾讯数字安全创新大赛在京正式启动。本次大赛由腾讯安全和中国产业互联网发展联盟联合主办&#xff0c;腾讯安全、KEEN、元起资本、赛博英杰、数世咨询等多家企业联合发起&#xff0c;中国产业互联网发展联盟安全专委会承办。 大赛旨在寻找网络安全新力…

oracle数据库无监听程序

在电脑---服务---启动oracle tns 如果还是出现错误的话&#xff0c;找到Net Manager&#xff0c;将网络的ip监听删除&#xff0c;将本机的主机名配好&#xff0c;即可打开tns服务 转载于:https://www.cnblogs.com/jiangsheng3/p/5025201.html

个人开发者即时到账收款方案 BufPay.com

BufPay 个人即时到账支付平台前言 作为独立开发者&#xff0c;一般只有一个人独立奋战&#xff0c;做出了产品需要收款是非常麻烦的&#xff0c;接入支付宝微信支付都需要公司公户&#xff0c;而注册公司、开公户等一系列操作非常麻烦&#xff0c;成本也很高一年也要 1w 左右。…

用 Python 制作数据大屏,超简单

作者 | 俊欣来源 | 关于数据分析与可视化今天我们用Streamlit模块来制作一个数据面板&#xff0c;将数据更加直观地呈现给别人观看&#xff0c;整个页面大致如下图所示&#xff1a;制作工具栏在页面的左侧是一个工具栏&#xff0c;工具栏中有多个按钮&#xff0c;分别是“About…

Oracle 12C -- 清空audit记录

1.使用job清空 SQL> dbms_audit_mgmt.create_purge_job(audit_trail_type> DBMS_AUDIT_MGMT.AUDIT_TRAIL_UNIFIED,audit_trail_purge_interval>12&#xff0c;audit_trail_purge_name>audit_trail_pj,use_last_arch_timestamp>TRUE,container>dbms_audit_mgm…

魔法引用函数magic_quotes_gpc和magic_quotes_runtime的区别和用法

PHP提供两个方便我们引用数据的魔法引用函数magic_quotes_gpc和magic_quotes_runtime&#xff0c; 这两个函数如果在php.ini设置为ON的时候&#xff0c;就会为我们引用的数据碰到单引号和双引号"是自动加上反斜线&#xff0c;帮我们自动转译符号&#xff0c;确保数据操作的…

Unity脚本生成插件:Script Create Dialog

最近写代码又犯懒了...感觉每次新建脚本都要写一堆简单重复的东西好无聊&#xff0c;所以搜索了一下有没有自动生成脚本的插件。结果还真被我发现了&#xff0c;官方在N久之前就制作了自动生成脚本的插件[Script Create Dialog]&#xff0c;大概是名字起的和脚本生成器相差太多…

多路IO复用模型 select epoll 等

同步阻塞IO在等待数据就绪上花去太多时间&#xff0c;而传统的同步非阻塞IO虽然不会阻塞进程&#xff0c;但是结合轮询来判断数据是否就绪仍然会耗费大量的CPU时间。多路IO复用提供了对大量文件描述符进行就绪检查的高性能方案。selectselect诞生于4.2BSD&#xff0c;在几乎所有…

可操作性强!Python实现一个电影订票系统!

来源丨Python小二一、效果展示通过Python实现一个电影订票系统&#xff0c;效果如下所示&#xff1a;二、整体结构图三、代码分解3.1 infos.py一部电影的详细信息适合用 字典 结构来存储&#xff0c;我们可以给字典里添加多个键值对来保存电影的名称、座位表和宣传时用的字符画…

centos7 install mysql

1. 下载mysql的repo源 $ wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm 2. 安装mysql-community-release-el7-5.noarch.rpm包 $ sudo rpm -ivh mysql-community-release-el7-5.noarch.rpm 安装这个包后&#xff0c;会获得两个mysql的yum repo源&#x…

unity加载ab后,场景shader不起效问题(物件表现黑色)

需要把unity自带的shader&#xff0c;加入到默认列表转载于:https://www.cnblogs.com/lancidie/p/9293827.html

Linux下各类TCP网络服务器的实现源代码

http://www.linuxeden.com/forum/t146870.html大家都知道各类网络服务器程序的编写步骤&#xff0c;并且都知道网络服务器就两大类&#xff1a;循环服务和并发服务。这里附上源代码来个小结吧。首先&#xff0c;循环网络服务器编程实现的步骤是这样的&#xff1a; 这种服务器模…

ReferenceQueue的使用

转&#xff1a;http://www.iflym.com/index.php/java-programe/201407140001.html 1 何为ReferenceQueue 在java的引用体系中&#xff0c;存在着强引用&#xff0c;软引用&#xff0c;虚引用&#xff0c;幽灵引用&#xff0c;这4种引用类型。在正常的使用过程中&#xff0c;我们…

红帽、Docker、SUSE 在俄罗斯停服

‍‍国际局势给技术圈带来的影响依然在蔓延。整理 | 苏宓出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;继 Oracle、Google、苹果等科技公司和 React 开源项目之后&#xff0c;如今 Linux 发行版也牵扯进俄乌之间冲突的漩涡中。其中一个是全球最大的独立开源软件公…

配置linux-Fedora系统下iptables防火墙

参考地址&#xff1a;https://blog.csdn.net/zhangjingyi111/article/details/78902820 本篇文章为实验课过程记录&#xff0c;较为简略。 1.查看系统是否安装iptables 命令&#xff1a;iptables --version 2.开启iptables 命令&#xff1a;service iptables start 出现错误&am…

output_buffering详细介绍

HTTP Header为什么要使用Output Buffering技术Output Buffering的工作原理基本用法高级用法使事情更为简单哈哈&#xff0c;我成功了我个人认为&#xff0c;Output buffering是比较纯粹的PHP4.0特征。尽管从概念上看来相当简单&#xff0c;但是output buffering功能非常强大&am…

12 个 Pandas 数据处理高频操作

作者 | 老表来源 | 简说Python今天给大家分享几个自己近期常用的Pandas数据处理技巧&#xff0c;主打实用&#xff0c;所以你肯定能用的着&#xff0c;建议扫一遍&#xff0c;然后收藏起来&#xff0c;下次要用的时候再查查看即可。简单说说总结分享统计一行/一列数据的负数出现…

ORACLE初次安装自动安装软件包

一、自动安装所需软件包提前配置好yum仓库定义package.txt包列表文件&#xff1a;以官网RHEL6为例&#xff0c;这里有compat-libstdc有两个包&#xff0c;如果不加*&#xff0c;号后面的compat-libstdc-33-3.2.3-69.el6.x86_64&#xff0c;compat-libstdc-296-2.96-144.el6.i68…

中文详解phpmailer所有对象和属性

2019独角兽企业重金招聘Python工程师标准>>> 2009-03-09 19:13:50 前言&#xff1a; phpmailer是一个优秀的发件程序&#xff0c;但中文资料比较少&#xff0c;于是有牛人手动翻译了phpmailer的elementindex.html,E文的&#xff1a;[url]http://www.bblog.com/api…

php error_reporting 详解

error_reporting设定错误讯息回报的等级。语法: int error_reporting(int [level]);传回值: 整数函式种类: PHP 系统功能内容说明 本函式用来设定错误讯息回报的等级&#xff0c;参数 level 是一个整数的位元遮罩 (bitmask)&#xff0c;见下表。value constant 1 E_ERROR 2 E_W…

mysql多个实例

2019独角兽企业重金招聘Python工程师标准>>> 1>、关闭原有的默认端口3306的mysql:service mysqd stop 2>、拷贝或创建数据文件 cp -r /data/mysql/data1 /data/mysql/data_3307 格式 用bin/mysql_install_db --basedirmysql的目录 --datadir数据存放的目录 …

10行 python 代码做出哪些酷炫的事情?

来源 | Python小二Python凭借其简洁的代码&#xff0c;赢得了许多开发者的喜爱。因此也就促使了更多开发者用Python开发新的模块&#xff0c;从而形成良性循环&#xff0c;Python可以凭借更加简短的代码实现许多有趣的操作。下面我们来看看&#xff0c;我们用不超过10行代码能实…

这是一个不一样的社会公益活动

公益不是每个人的刚需&#xff0c;但是可以&#xff0c;以全链条模式联动更多人需求。 社会公益就是给社会带来帮助的事或物&#xff0c;它包含社区服务&#xff0c;环境保护&#xff0c;知识传播&#xff0c;公共福利&#xff0c;帮助他人&#xff0c;社会援助&#xff0c;社会…

剖析PHP中的输出缓冲

剖析PHP中的输出缓冲 本文按署名非商业用途保持一致授权作者: &#xff0c;发表于2005年12月24日01时54分 我们先来看一段代码。<?php for ($i10; $i>0; $i--) {echo $i;flush();sleep(1); } ?>按照php手册里的说法该函数将当前为止程序的所有输出发送到用户的浏览…

luasocket 安装记录 (FS1.6)

说明&#xff1a; 想通过Lua 脚本实现 http。默认 FS 的 mod_lua 中没有对socket 的支持&#xff0c;如下的操作为lua 添加 socket的支持。 一、下载 luasocket 包&#xff1a; # wget http://luaforge.net/frs/download.php/2664/luasocket-2.0.2.tar.gz # tar zxvf luaso…

5个实用的例子,一行 Python 能干嘛?

作者 | 菜鸟哥来源 | 菜鸟学Python一行Python到底能干嘛&#xff0c;今天给大家分享几个不错的小例子&#xff0c;都是在实际工作中经常会碰到的例子&#xff0c;让你知道一行代码的威力&#xff0c;让菜鸟也能秒变王者&#xff0c;尤其是能镇住新来的学妹。01、如果你是HR你手…