Django Rest Frameworkの限定的なXSSバグ
こんにちは。韓国でウェブハッキングを(特に offensive security )しているユンソクチャンともうします。最近、日本語を勉強していて、日本で仕事をしたいと思いますので、この文は日本語でも翻訳します。
じゃあー、よろしくお願いします。
3行要約
- Django Rest Frameworkで特定の状況においてXSS脆弱性が発生する可能性があることを探しました。
- 実際には『セキュリティ脆弱性』ではなく、このような Potential Bug が発生する可能性がある程度の問題です。
- 日本が好きな韓国人として、日本人ハッカーのみなさんと仲良くなりたいです。
Intro
今年のはじめ、Django Rest Frameworkのソースコードを分析し、XSS攻撃に脆弱なコードを探しました。このイッシュは Django Security チームには報告したので、私の blog にこうやって報告書の感じで整理してみます。
みなさんは下のコードで XSSができると思いますか。それならば、なぜできたのだと思いますか。
(上のコードで、path() で指定された path ‘api/’にアクセスすれば、下のようなREST APIの例示の画面が見えます。)
このコードでXSS脆弱性を見つける方は少ないと思います。なぜなら、rest_framework.response.Responseクラスを使用する際、XSSが発生することを説明している文書がないためです。
それじゃあ、この文では rest_framework.response.Response クラスでヘッダーを直接に指定することがなぜ危ないのかを説明します。
Django Rest Framework
Django Rest Framework (DRF) とは、Djangoで作られたweb serverにてHTTPを基盤としたREST APIを簡単に具現できるよう手伝う3rd partyライブラリです。最近は Django Ninjaというライブラリが人気ですが、何年前までも Djangoを基盤にREST APIを具現するのためには、DRFライブラリくらいしかありませんでした。私がこの文を書いている現在、Githubで27.6kものスターを得るほど、広く使われてるライブラリだと言えます。
そして、Django Security Teamを通して security reportをもらっているから、実際には公式のライブラリだと思います。
DjangoはどうやってXSSを防ぐのか
DjangoはJinja基盤のTemplate機能でHTMLコードを生成します。この際、Djangoから作れるHTMLコードには使用者が入力した値が入られますが、DjangoのTemplate機能はどうやってXSSを防御するのかしってみます。
上のコードはDjangoでTemplateを生成するための`render()`というメソッドを通して、使用者の入力値をtemplateへ渡す例示です。
上のpythonコードで使用されるtemplateであるtest.htmlです。test.htmlファイルは、viewメソッドからnameの値をもらって {{name}}
文字列を変えます。
実際に以前に作成されたviewにHTTP要請を送れば、HTML entity エンコーディングを通して、特殊文字が変わったことが見られます。それでは、Django Template機能を使用するには、特殊文字のエンコーディングの使用したい時は、Djangoのtemplate filterである`safe`を使います。
safeフィルターはテンプレートの中で上みたいなコードで使えます。
今回は、特殊文字がエンコーディングされないでそのままに出力されました。
safe フィルターのソースコード分析
それではsafeというtemplate filterの作動方法を簡単に分析してみます。
safe template filterでは、使用者からもらう値を持って内部的にmark_safe()というメソッドを実行する後、リターンします。このmark_safe()メソッドのなかではSafeStringというクラスを通して、一般的なstr typeのデータを明示的にSafeStringクラスのオブジェクトに変換しています。
このSateStringクラスはDjango Templateの中で、「もう処理しまったから安全なデータ」だと判断されます。従って、SafeStringタイプのデータの特殊文字はもうHTML entityでエンコーディングされないです。
ではー
- safeというtemplate filterを使用したり
- mark_safe()というメソッドを使用して
使用者の入力が入っているデータを処理すれば、XSSが発生する可能性があると思えますね。🤔
Django Rest Frameworkのソースコード分析
今度はDRFライブラリのソースコードを分析して、APIViewクラスがどうやってHTTP Responseを作るのか知ってみます。
rest_framework/settings.py
rest_framework/settings.pyを見れば、rest_framework.renderers.BrowsableAPIRendererというクラスがDRFのAPIViewクラスの基本レンダーラだとことを知れます。続いて、このBrowsableAPIRendererクラスを分析して見ます。
rest_framework/renderers.py
BrowsableAPIRendererクラスでは、DRFがrest_framework/api.htmlファイルの基盤でTemplateレンダーリングして、API Documentation画面をリターンすることが分かります。
rest_framework/api.html
前に使用されたapi.htmlファイルは、また同じディレクトリ内のbase.htmlファイルを使用します。
rest_framework/base.html
rest_framework/base.htmlファイルではresponse_headersという変数をbreak_long_headersというtemplate filterを通して、出力しています。最後にこのbreak_long_headersを分析してみます。
break_long_headers
Template Filter
このtemplate filterではheaderという変数をもらっています。この変数には、HTTPの応答headersのkey、value中のvalueの値が入って来ます。ところでこの関数…さき見たmark_safe()メソッドを使用しています。そうして、mark_safe()に入って行く値であるheaderでは、使用者が入力した文字列が入っている可能性があります。
Exploitation
それでは、また初めのviews.pyのコードをもう一度分析してみます。
このコード…使用者からもらう値をSet-Cookieという応答ヘッダーに入れています。さきの分析した結果、このようなコードはXSSができますから、
http://localhost:8000/api/?foo=<img+src=x+onerror=prompt()>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
おつかれさまでした!XSS成功です。
しかし…
しかし、このXSSは完璧なく脆弱性です。
まず、HTTP応答ヘッダーにユーザーの入力値が含まれる場合はほとんどありませんです。ユーザー入力が含まれるheaderは、Set-Cookie、Location、Content-Location程度でしましょう。
次に、もしResponse Headerにユーザーの入力値が含まれるとしても、RFC 3986に従って、その値は必ずURLエンコーディングされる必要があります。さらに、Set-Cookieヘッダーの場合、通常はResponseクラスのset_cookie()メソッドを通じて指定され、このメソッドにはURLエンコーディングのコードが含まれています。
したがって、標準的なセキュリティ規格を遵守してコーディングされていれば、絶対に発見されることのない脆弱性です。Djangoセキュリティチームもこの点を理由に、今回の問題を脆弱性として扱わないと回答しました。
結論
- RFC規約に従ってセキュアコーディングをしましょう。
- 基本機能を利用しましょう。
- セキュリティホールとして認められず残念でした。
++ 追加
6月14日、私が情報したバグがmasterブランチでなおされましたね!😎
そして6月26日、このバーグで「CVE-2024-21520」をもらいました。
二週間ぐらいかけて日本語の辞書を引きながら書いた文章ですが、どうでしょうか?面白く読んでいただけたなら、私のプロフィールも見ていただけると嬉しいです!