標籤:

rubygems.org遠程命令執行漏洞分析

通過rubygems.org上的反序列化漏洞執行遠程代碼, 是 Ruby 社區的一個非常受歡迎的託管服務。不過目前該漏洞的補丁已經發布,請點此升級到最新的版本。這個漏洞已被官方命名為CVE-2017-0903,關於該漏洞的詳細官方介紹請點此。

如果你曾經編寫過ruby應用程序,那麼很可能你已經和rubygems.org進行過交互了。甚至你可能已經把該網站設置為信任,以允許它在你的計算機上運行任意程序,例如,當用gem命令安裝rails時,gem程序就會從rubygems.org獲取rails gem及其所有依賴項,並將所有內容安裝到相應的位置,這樣任何擁有賬戶的人都可以在後台發布gem命令了。

Rubygems.org本身就是一個rails應用程序,它有著清楚地信息披露條例。

遠程命令執行漏洞分析

Ruby gems實際上只是tar文件,所以運行tar -xvf foo.gem通常會留給你三個文件:

metadata.gzndata.tar.gznchecksums.yaml.gzn

這三個文件都是以.gz結尾,屬於被壓縮的文件。 metadata.gz包含一個YAML文件,包含有關gem的信息,如名稱,作者,版本等。 data.tar.gz包含了另一個tar文件,該文件包含所有源代碼。 checksums.yaml.gz包含一個YAML文件,其中包含gem命令的一些哈希加密。

不過我發現,解析不信任的YAML是危險的。原來,我一直認為它是一種類似JSON的良性交換格式,但事實上,YAML允許任意對象的編碼,就像利用Python pickle可以實現任意代碼執行。

當你將gem上傳到rubygems.org時,應用程序將調用Gem::Package.new(body).spec。該方法所用的rubygems gem使用了不安全的YAML.load調用來載入gem中的YAML文件。

不過,rubygems.org的作者是知道該方法的安全隱患的,在2013年以前,開發者利用給內置對象擴展方法(Monkey Patching)修補了YAML和gem解析庫,僅允許對白名單里的對象進行反序列化。到2015年則完全採用了Psych.safe_load。

不幸的是,monkey-patching的修復還是遺留了一些漏洞,因為它只修補了Gem::Specification#from_yaml方法。如果我來看看在調用到#spec時所發生的一些情況,我就會明白#verify的調用,下面是調用中的一些關鍵部分:

# ...n @gem.with_read_io do |io|n Gem::Package::TarReader.new io do |reader|n read_checksums readern verify_files readern endn endn verify_checksums @digests, @checksumsn# ...n

然後,在#read_checksums中會發生以下進程:

# ...n Gem.load_yamln @checksums = gem.seek checksums.yaml.gz do |entry|n Zlib::GzipReader.wrap entry do |gz_io|n YAML.load gz_io.read # oopsn endn endn# ...n

現在,我就可以用我控制的輸入調用YAML.load。最初,我試圖在YAML.load調用時運行漏洞利用代碼。但事實比我想得更複雜,雖然我可以反序列化任意對象,但其實對這些對象進行調用的方法卻非常有限。是我可以對這些對象做出的唯一實際方法是非常有限的。 這時,就要在python上使用yaml解析庫庫,這可以讓我多一些調用方法的選擇,比如#[]=, #init_with,和#marshal_load(請注意不是Marshal.load)。但是對於大多數對象來說,這些方法並不會給攻擊帶來什麼靈活性,因為他們通常的做法只是初始化幾個變數並返回。在一些標準的rails庫中存在一些危險的#[]=方法(如過去一樣),但目前,我還沒有找到一個能夠攻擊的對象。

於是,我又重新檢查了rubygems.org應用程序,對其中的@checksums變數的作用進行重新評估,發現可以將其設置為任何類實例變數,在#verify_checksums中的情況如下:

# ...n checksums.sort.each do |algorithm, gem_digests|n gem_digests.sort.each do |file_name, gem_hexdigest|n computed_digest = digests[algorithm][file_name]n# ...n

如果我可以構建一個調用#sort的對象,那就可以實施一些攻擊,觸發漏洞。這樣,我就有了以下的POC。實際得到評估的有效載荷包含在底層64位編碼的DEFLATE壓縮的編組部分,在本例中,它只是負責運行echo "oops"。

SHA1: !ruby/object:Gem::Package::TarReadern io: !ruby/object:Gem::Package::TarReader::Entryn closed: falsen header: foon read: 0n io: !ruby/object:ActiveSupport::Cache::MemoryStoren options: {}n monitor: !ruby/object:ActiveSupport::Cache::Strategy::LocalCache::LocalStoren registry: {}n key_access: {}n data:n 3: !ruby/object:ActiveSupport::Cache::Entryn compressed: truen value: !binary n eJx1jrsKAjEQRbeQNT4QwQ9Q8hlTRXGL7UTFemMysIGYCZNZ0b/XYsHK8nIOn nDtRBGbvJDzxMuRMLABHzIzOSqD0G+jbVMQmhzfLwd4jnphebwUrE0ZAoJrzn YQpLE0PCRKGCmSnsWr3p0PW000S56G5eQ91cv9oDpScPC8YyRIG18WOMmGD7n /1X1AV+XPlQ=n

可以看出,從最後一步才開始逆向進行#sort調用。

在底部,我有一個ActiveSupport::Cache::Entry對象。這個對象的重要之處在於,當#value方法被調用並且@compressed為true時,它將在攻擊者提供的DEFLATE壓縮的數據上調用Marshal.load。解組的對象的構造方式是這樣的,只要調用其上的任何方法就可以執行攻擊者的代碼。該方法是我以前寫的,工作原理請點擊這裡。不幸的是,我不能在實現代碼執行時,只用YAML來反序列化這個對象,因為它幾乎對所有的方法都進行了undef,包括允許我設置實例變數的方法。因此,在使用時,要對Marshal.load進行載入才可以。

我會利用ActiveSupport::Cache::MemoryStore對象在@data哈希中解組我的惡意對象。它的父類ActiveSupport::Cache::Store定義了一個在MemoryStore中調用#read_entry的#read方法,#read_entry基本上只是抓取@data中的條目並將其返回。

由於MemoryStore以反序列化後的數組或者序列化後的位元組緩存(ByteBuffer)形式將代碼塊存儲到內存中,所以對MemoryStore#read的調用來自對Gem::Package::TarReader::Entry#read的調用,而Gem::Package::TarReader::Entry#read本身是由Gem::Package::TarReader#each調用的。讀取返回後,對返回的值調用#size,由於我的惡意解組對象未定義,所以這會導致我的有效載荷開始執行。

最後,因為Gem::Package::TarReader對可枚舉性(enumerable)進行了指定,所以調用其#sort方法將會調用其#each方法,這樣整個攻擊鏈就被啟動了。

總結

在本文中,我介紹了 YAML的強大功能,有時它也可以在表現力較弱但很安全的交換格式(如JSON)中使用。也許在將來,YAML.load可以被修改為將類的白名單作為可選參數,使複雜對象的反序列化成為選擇性行為。其實,目前的YAML.load實際上應該被命名為類似YAML.unsafe_load這樣的名稱,這樣用戶就知道他們何時用YAML.safe_load了。

本文翻譯自:justi.cz/security/2017/ ,如若轉載,請註明原文地址: 4hou.com/vulnerable/791 更多內容請關注「嘶吼專業版」——Pro4hou

推薦閱讀:

CVE-2017-8715分析:利用PS模塊清單文件繞過微軟安全補丁
網路犯罪分子越來越擅長使用先進的身份驗證方法
安全獨白 甲方視角下的威脅情報服務

TAG:信息安全 |