哈希拒绝服务攻击

lua-users home
wiki

Lua 5.1、Lua 5.2.0 和 LuaJit 2.0.0-beta9 中使用的字符串哈希算法容易受到哈希拒绝服务 (DoS) 攻击(参见 Lua 邮件列表上的主题 [1])。

哈希攻击基准测试

在下面的基准测试中,所有字符串都具有相同的长度,“良好”字符串的哈希冲突很少,“不良”字符串的哈希值都相同。使用 32 字节的字符串长度,因为很容易攻击所有测试的 VM 实现(除了 Lua 5.2.1-work1,因为它将对整个字符串进行哈希)中使用的哈希算法。LuaJIT 2.0.0-beta9 容易受到长度为 15 的字符串的哈希 DoS 攻击。

--------- Simulated HTTP POST attack
-- 40000 unique Strings, length 32, estimated POST data length 1.328Mbytes
==============================================================
                               Good strings,       Bad strings
--------------------------------------------------------------
Lua 5.1                        0.430 secs          29.753 secs
Lua 5.1 (second hash fix)      0.452 secs           0.515 secs
Lua 5.2.0                      0.450 secs          29.850 secs
Lua 5.2.1-work1                0.450 secs           0.380 secs
LuaJit 2.0.0-beta9             0.086 secs          11.988 secs

哈希算法分析

-- number of bytes not used in hash function
==============================================================
String length                  < 15,  15-20 ,  20-32 , 32-64  
--------------------------------------------------------------
Lua 5.1                        0   ,    0   ,    0   , len/2
Lua 5.2.0                      0   ,    0   ,    0   , len/2
LuaJit 2.0.0-beta9             0-1 ,   2-4  , len-16 , len-16

攻击者只需要 3 个未在哈希函数中使用的字节,就可以生成超过 1600 万个具有相同哈希值的字符串(所有字符串都需要具有相同的长度)。对于 Lua 5.1 和 5.2.0,所需的最小字符串长度为 32 字节,对于 LuaJit 2.0,所需的最小长度仅为 17 字节。

Lua 5.1 的第二个哈希修复

[下载 Lua 5.1 的第二个哈希修复补丁]

功能

工作原理

-- pseudo code, This is not true Lua code

function NewStringMarker(state, hash)
  local marker = {len = 0, is_marker = true, hash = hash, refcount = 0}

  local bucket = GetBucketHead(state, hash) -- get bucket
  Append(bucket, marker)
  return marker
end

function NewString(state, str, len, hash, has_marker)
  local elem = { len = len, has_marker = has_marker, hash = hash, str = str }

  local bucket = GetBucketHead(state, hash) -- get bucket
  Append(bucket, elem)
  return elem
end

function ReleaseStringMarker(state, str, len)
  -- rehash string with first hash function to find marker.
  local hash1 = FirstHashFunc(str, len, state.seed)
  local bucket = GetBucketHead(state, hash1)
  foreach(elem in bucket) do
    -- check if hash matches
    if (elem.hash == hash) then
      -- check if this is a marker
      if (elem.is_marker) then
        if (--(elem.refcount) == 0) then
          -- free unused marker and remove from bucket
          Remove(bucket, elem)
          return -- done
        end
      end
    end
  end
end

-- called by GC
function FreeString(state, elem)
  if (elem.has_marker) then
    -- find marker using first hash function
    ReleaseStringMarker(state, elem.str, elem.len)
  end
  assert(not elem.is_marker)
  -- free string element.
end

function LuaNewInternString(state, str, len)
  local first_hash = true
  local depth = 0
  local marker = nil
  local has_marker = false
  local hash1 = 0
  local hash = 0 -- current hash
  local bucket = nil

  hash1 = FirstHashFunc(str, len, state.seed)
  hash = hash1
rehashed:
  bucket = GetBucketHead(state, hash) -- get bucket using current hash value.

  foreach(elem in bucket) do
    -- check if hash matches
    if (elem.hash == hash) then
      -- check if this is a marker
      if (elem.is_marker) then
        marker = elem
      else
        -- check if this is the string we are looking for.
        if (elem.len == len and elem.str == str) then
          -- found string
          return elem
        end
      end
    end
    depth++
  end
  -- string not found
  if (first_hash) then
    -- check if there is already a marker for the first hash
    if (marker) then
      -- we need to search for the string using second hash function.
      has_marker = true
      hash = SecondHashFunc(str, len, hash1) -- seed second hash with first hash value
      depth = 0
      first_hash = false
      goto rehashed
    end
    -- if bucket is over limit
    if (depth > DEPTH_LIMIT) then
      hash = SecondHashFunc(str, len, hash1) -- seed second hash with first hash value
      -- create marker
      marker = NewStringMarker(state, hash1)
      marker.refcount++
      has_marker = true
    end
  else
    -- second hash function was used, inc. reference counter of marker.
    marker.refcount++
  end
  -- create new string
  return NewString(state, str, len, hash, has_marker)
end

最近更改 · 首选项
编辑 · 历史记录
最后编辑于 2012 年 5 月 1 日上午 10:01 GMT (差异)