CVE-2020-8264(RailsのXSS)解説
Ruby on Rails 6.0.3.4で修正されたCVE-2020-8264の解説です。
- https://groups.google.com/g/rubyonrails-security/c/yQzUVfv42jk
- https://hackerone.com/reports/904059#activity-8945588
ActionDispatch::ActionableExceptions
問題があったのはRails6.0から組み込まれた ActionDispatch::ActionableExceptions
です。(https://github.com/rails/rails/blob/v6.0.3.1/actionpack/lib/action_dispatch/middleware/actionable_exceptions.rb#L7)
module ActionDispatch
class ActionableExceptions # :nodoc:
...
def call(env)
request = ActionDispatch::Request.new(env)
return @app.call(env) unless actionable_request?(request)
ActiveSupport::ActionableError.dispatch(request.params[:error].to_s.safe_constantize, request.params[:action])
redirect_to request.params[:location]
end
private
def actionable_request?(request)
request.show_exceptions? && request.post? && request.path == endpoint
end
def redirect_to(location)
body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
[302, {
"Content-Type" => "text/html; charset=#{Response.default_charset}",
"Content-Length" => body.bytesize.to_s,
"Location" => location,
}, [body]]
end
end
end
CVE-2020-8185
ActionableExceptionsは開発時に使うmiddlewareですが、request.show_exceptions?
は開発モードに限定されない問題があり、なおかつ RAILS_ENV=production
であってもmiddlewareが読み込まれていました。
XSSの危険性
ERB::Util.unwrapped_html_escape
はHTMLタグをエスケープするものであり、javascript:
や data:
などのschemeによるXSSには対応していません。(https://github.com/ruby/erb/blob/v4.0.2/ext/erb/escape/escape.c)
そのため、 request.params[:location]
へ javascript:alert(location)
などの値が渡せた場合にはリンクをユーザがクリックするよう誘導することでXSSが可能です。
<a href="javascript:alert(location)">redirected</a>
https://example.com/rails/actions?location=javascript:xxx&error=...
のような画面を開いた場合にXSS可能のようにも見えますが、これはブラウザにブロックされます。
ヘッダに Location: javascript:xxx
という値は設定できないためです。
(http://www.slideshare.net/harupu/denatechcondenasameorigin-policy#33)
HTTP Response header injection
この脆弱性を報告した半年ほど前にPumaのHTTP Response header injectionが公開されました。
当時はPuma以外にもheader injectionに脆弱なアプリケーションサーバがいくつかあり、ActionableExceptionsのURLに \r
の文字を入れることでHTTP Response header injectionが可能でした。
一方、 \n
の文字はCookieなどの複数行のヘッダを扱うのに利用されているため、Response bodyの改ざんは出来なかったはずです。(https://github.com/puma/puma/blob/v6.1.0/lib/puma/request.rb#L627)
HTTP Response header injectionと組み合わせてのXSS
ここでXSSを成立させるために細工をします。
Pumaの修正コミットを確認すると \r
がある場合にはヘッダの値を除外しておりエラーにはなりません。
試してみると \r
を先頭に入れた場合HTTP status codeとしては302ですが、ヘッダーの Location
の値は空になりリダイレクトは行われずbodyの内容がそのまま表示されます。
HTML中では空白として扱われる文字は無視されるため \rjavascript:alert(location)
をリダイレクト先のURLとして渡すとXSS可能でした。
<html><body>You are being <a href="
javascript:alert(location)">redirected</a>.</body></html>
CSRF対策とCSPの回避
攻撃に必要なリクエストはPOSTですが、RailsのCSRF対策は動作していませんでした。Rackがmiddlwareを処理していく途中でレスポンスが作られるため、CSRF防止のコードが実行されないためです。
同様にRailsで設定したCSPを設定した場合であっても、ActionableExceptionsがレスポンスを作成してしまう都合上、CSPのヘッダが出力されないため回避できました。
Clickjacking
XSSが発火するためにはユーザによるクリックが必要であり、罠サイトから遷移で必ず攻撃できるわけではありません。しかしCSPヘッダが出力されていないのを利用してClickjackingによる誘導が可能です。
罠サイト上にiframeを用意し、アクセスがあったら自動でRailsサーバへ遷移してリンクを表示します。
画像ではiframeの内容を半透明にしていますが、完全な透明にしてマウスポインタに追随するとより危険です。
開発サーバへのRCE
web-consoleは開発時にデバッグとして便利なgemですが、ブラウザからのリクエストで任意コードを実行する機能があります。CSRF対策はされているのですが、画面内でXSSが利用できる場合には回避可能です。
よって、ActionableExceptionsとの組み合わせにより、Rails6.0.0から6.0.3.4未満のRails開発サーバを起動していた場合にブラウザで罠サイトをクリックすると、マシン上で任意のrubyのコードが実行できるという状態でした。
Discussion