💭

CVE-2020-8264(RailsのXSS)解説

2023/02/18に公開

Ruby on Rails 6.0.3.4で修正されたCVE-2020-8264の解説です。

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が公開されました。

https://blog.tokumaru.org/2022/12/CVE-2021-3362.html

当時は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可能でした。

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