2022年12月20日火曜日

Ruby cgi gemのHTTPヘッダインジェクション脆弱性CVE-2021-33621の概要と発見の経緯

この記事はRuby Advent Calendar 2022の第20日の記事です。前日の記事は@ydahさんによる「RuboCopのバージョンを最新に保つ技術」でした。

2022年11月22日に、Ruby cgi gemのHTTPヘッダインジェクション脆弱性CVE-2021-33621が発表がされました。

私はHackerOneを通じてこの脆弱性を報告しました。この記事では、当該脆弱性の概要と発見の経緯などについて報告します。

概要

この脆弱性はRubyでCGIプログラムを記述際に用いられるcgi gem のheaderメソッドに内在するものです。PoCを以下に示します。このスクリプトは、クエリ文字列 num を受け取り、http://example.jp/?num=<指定された数値>にリダイレクトするものです。

#!/usr/bin/ruby
require 'cgi'

cgi = CGI.new
num = cgi.params['num'][0]
print cgi.header({'status' => '302 Found', 'Location' => "http://example.jp/?num=#{num}"})

ここで、numとして下記を指定した場合、

num=3%0d%0aSet-Cookie:+SESSID%3d1234567890

%0d%0aは改行なので、以下のレスポンスヘッダが出力されます。

Status: 302 Found
Location: http://example.jp/?num=3
Set-Cookie: SESSID=1234567890

すなわち、リダイレクトする「ついで」に任意のクッキーをセットすることができます。

クッキのセット以外に、任意URLへのリダイレクトもできます。

num=3%0d%0aLocation:+http://trap.example.com/

この結果のレスポンスヘッダは下記となりますが、

Status: 302 Found
Location: http://example.jp/?num=3
Location: http://trap.example.com/

Apacheの場合複数のLocationヘッダがあった場合、最後のLocationヘッダのみがブラウザに返されるので、結果として、trap.example.comにリダイレクトされます。これにより、偽ログイン画面に遷移してパスワードを盗むなどができます。

また、以下のようにHTTPレスポンスボディを出力することもできます。

num=3%0d%0a%0d%0a<script>alert(1)</script>

レスポンスは下記となります。空行の後にJavaScriptコードが出力されていることが分かります。

Status: 302 Found
Location: http://example.jp/?num=3

<script>alert(1)</script>

ただ、このままだとリダイレクトが優先されるのでレスポンスボディは表示されません。リダイレクトを避けるテクニックとして、ヘッダインジェクションによりStatus 500等を出力する方法があります。このテクニックははるぷさんに教えていただきました。

num=3%0d%0aStatus:+500%0d%0a%0d%0a<script>alert(2)</script>

このケースでの出力は下記となります。

Status: 302 Found
Location: http://example.jp/?num=3
Status: 500

<script>alert(1)</script>

Statusヘッダが2つありますが、前述のようにApacheは最後のStatusヘッダのみをブラウザに返すため、リダイレクトは無効となり、JavaScriptが実行されます。これはXSSと同等の脅威となります。

脆弱性発見の経緯

なぜ今どきRubyのcgi gemを調べたか、それにはちょっとした理由があります。 元々は、2020年1月にRuby on RailsのHTTPヘッダインジェクションに脆弱性を見つけて、HackerOneにて報告したところ、これはRailsの脆弱性ではなくて、ウェブサーバーのPumaの脆弱性だということになり、Puma側で修正された(CVE-2020-5247)ことがきっかけです。

この脆弱性について、適当な場で紹介したいと思っていたところ、2021年6月18日に銀座Rails#34@リンクアンドモチベーションがオンライン開催されることがわかりましたので、CfPに応募して採択されました。

この内容は私のYouTubeチャンネルにて公開しています。動画データを主催者様に提供いただきました。ありがとうございます。

この発表(動画)では、冒頭のHTTPヘッダインジェクション入門をPHP(の古いバージョン)にて行っていますが、元々はRubyのCGIで説明するアイデアがありました。そのサンプルプログラムを作成する過程でRubyのcgi gemに脆弱性があることに気づきました。ゼロデイの脆弱性を説明に使うわけにもいかず、本番ではPHPの旧版の脆弱性を用いた形になりました。

さて、cgi gemに脆弱性があることは疑いのないものでしたが、「RubyでCGIを書いているサイトはどれくらいあるのだろう」という疑いもあり、gem側で修正しなくても、注意喚起してアプリケーション側での改修でもよいのではないかと思いました。とはいえ、当方が勝手に「ゼロデイ脆弱性」を公開することもよくありません。

このためRubyコミッタ柴田さんに相談したところ、チームで検討するのでHackerOne経由で申告してほしい(日本語でOK)旨の連絡をいただきましたので、HackerOneに投稿した内容が冒頭で紹介したものです。こちらは脆弱性と認められ、改修されることになりました。 また、CGI::Cookieにもセキュリティ上好ましくない実装を見つけましたので、追加の報告として提出しました。

内容は、Cookieのname、path属性、domain属性設定時に外部由来の値を使うとインジェクションが可能というものでした。こちらも脆弱性と認められ、同時に修正されています。詳しくはHackerOneの報告を御覧ください。

影響を受けるアプリケーション

当脆弱性の影響を受けるアプリケーションは下記のとおりです。

  • cgi.headerメソッドに外部由来の値をセットしている場合
  • CGI::CookieによりCookieのname、path属性、domain属性設定時に外部由来の値を使っている場合

※ Cookieのpath属性およびdomain属性へのセミコロンを用いたインジェクションは、PHP-7.2以前にも存在していました(バグ#69948)。

影響

当脆弱性の影響は概要で示したとおりですが、まとめると以下のようになります。

  • 任意のレスポンスヘッダの追加・改変
    • (その結果)クッキーの追加、改変
    • (同上)任意URLのリダイレクト
  • レスポンスボディの追加・改変
    • (その結果)任意JavaScriptの実行(XSSと同等の脅威)
  • Cookieのname、path、domainを通じた属性等の改変

対策

cgi gemを最新版(0.3.5, 0.2.2, 0.1.0.2)にアップデートすることで対策されます。

Ruby本体のバージョンアップ

Rubyのバージョン 3.1.3 / 3.0.5 / 2.7.7 にて最新のcgi gemを含む形で対応されています。 現時点では、主要Linuxディストリビューションはアップデートを提供していないようです。

cgi gemのアップデート

前述のように、Linuxディストリビューション経由でのyumやaptによるアップデートでは本問題の対応はできませんが、gemコマンドによるアップデートは可能です。

$ sudo gem update cgi

ただし、Rubyのバージョンが2.7以降である必要があります(参考)。Ruby 2.7未満を使用している場合は、Ruby自体をバージョンアップする(推奨)か、以下のアプリケーション側の対策を実施してください。

アプリケーションでの対応

アプリケーション側で対策するには、以下を実施してください。

  • cgi.headerメソッドに渡すパラメータを検証して改行(\rや\n)が混入しないようにする
  • CGI::Cookieに渡すname属性やdomain属性を検証する

まとめ

Ruby cgi gemのHTTPヘッダインジェクション脆弱性CVE-2021-33621について、脆弱性の内容と経緯について説明しました。今時RubyでCGIプログラムを記述するケースもほとんどないとは思いますが、HTTPヘッダインジェクションという今となってはレアな脆弱性が発見され、きちんと修正されたことをご報告したいと思い本稿を書きました。

脆弱性そのものは拙著「安全なWebアプリケーションの作り方」に記載されている範囲のものですので、バグハンターを目指す皆さんは、「著名ソフトウェアだけどあまり使われていない機能」に着目すると、比較的容易に脆弱性が見つけられ、インターネットの安全にも貢献できるのではないでしょうか。

また、今回の脆弱性対応くださった柴田(@hsbt)さん、@mameさん、@nobuさんに感謝申し上げます。きちんと対応いただき、発見者としてうれしく思います。ありがとうございました。

フォロワー

ブログ アーカイブ