Hash Dos 攻擊

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 (差異)