setattrの危険性:PythonにおけるMass Assignmentの脆弱性を防ぐ
Jack Hair
2023年2月15日
0 分で読めますMass Assignment(自動バインディングまたはオブジェクトインジェクションとも呼ばれます)は、ユーザー入力がプログラム内の変数やオブジェクトにバインドされる際に発生する脆弱性の一種です。
Mass Assignmentの脆弱性は、攻撃者がオブジェクトに予期しないフィールドを追加し、プログラムのロジックを操作することで発生することがよくあります。有名なGitHubの認証脆弱性もこれが原因でした。Ruby on RailsのMass Assignment機能を悪用した攻撃者が、自分の公開鍵をrailsのGitHub組織に追加し、masterにコミットをプッシュできたのです。
このブログ記事を書くきっかけの一つとなったのは、Springフレームワークに含まれる自動バインディング機能が原因で発生したリモートコード実行(RCE)脆弱性、Spring4Shellの脆弱性です。これを受けて、Pythonエコシステムにも同様の脆弱性が存在するかを調査しました。
この記事では、Pythonのsetattr()関数に潜むリスクを検証し、アプリケーションでMass Assignmentの脆弱性を防ぐ方法を解説します。
setattrの概要
PythonでMass Assignmentを実装する一般的な方法の一つが、組み込み関数のsetattr()です。この関数は、オブジェクト、属性名、属性値を受け取り、指定された値をオブジェクトの属性に設定します。新しい属性の追加にも、既存の属性の変更にも使用できます。
便利な一方で、オブジェクトのあらゆる属性の値をユーザーが設定できるようにすると、意図しない結果を招くおそれがあります。
第一に、従来型のMass Assignmentによるロジックの脆弱性がプログラムに生じる可能性があります。ユーザーによる変更を想定していなかったオブジェクトのフィールドも、変更できてしまいます。
第二に、setattrで変更できる属性は、プログラマーが定義したフィールドに限られません。関数や、 __class__、__dict__ などの組み込み属性も設定できます。Spring4Shellの脆弱性とは異なり、setattrではオブジェクトのネストされた子属性を設定できないため、攻撃の範囲は限定されます。しかし、setattrの呼び出し時に__class__を文字列値に設定したり、プログラムの実行中にオブジェクトに属する関数を上書きしたりすることで、簡単に例外を発生させることができます。このような例外は、プログラムの異常な動作やサービス拒否につながる可能性があります。
以下は、setattrを使ってユーザーを作成する簡単な例です。入力first_name=Sam,last_name=Sで実行すると、Sam S: Permission deniedと出力されます。
捕捉されない例外によってプログラムをクラッシュさせるには、入力first_name=Sam,last_name=S,is_admin=fooを使用できます。これによりis_admin関数が上書きされて文字列に置き換わり、関数の呼び出し時に例外が発生します。
入力first_name=Sam,last_name=S,admin=fooは、Userオブジェクトのadmin属性を文字列fooで上書きします。これはTruth(y)値だからです。is_admin関数がadmin属性を確認すると、ユーザーは管理者として扱われ、Sam S: You are admin!と出力されます。
Mass Assignmentを防ぐ
Mass Assignmentの脆弱性を軽減する一般的な方法は、ユーザー入力を含むオブジェクトと、プログラム内部のロジックを担うオブジェクトを分離することです。そのために、ユーザーが設定できるフィールドだけを含むデータ転送オブジェクト(DTO)を作成します。Pythonではdataclassesを使えば簡単に実装できます。以下のコードでは、DTOを使って許可するフィールドを明示的に定義しています。正しくないフィールドが指定されるとTypeErrorが発生し、これを捕捉して処理することで、前述の攻撃を防ぎます。
ユーザー入力の検証も、Mass Assignmentの脆弱性を防ぐ方法の一つです。常に、禁止データを確認するよりも、承認済みデータのリストを使って検証する方が適切です。以下のコードではsetattrを引き続き使用しながら、属性名が許可された属性のリストに含まれているかを確認します。
プログラムが想定するユーザー入力は、常に明示するのが最善です。ただし、ユーザー入力の処理に柔軟性が必要な場合は、setattr関数以外の方法もあります。Python標準ライブラリのcollectionsモジュールには、便利なクラスが2つ用意されています。collections.UserDictとcollections.abc.MutableMappingです。これらのクラスを継承することで、キーと値のマッピングに機能を追加できます。キーではなく属性を使って値にアクセスする必要がある場合は、クラスに__getattr__メソッドを実装します。以下の例では、Userクラスが辞書のように動作し、ユーザーが指定した任意のフィールドを保存できます。__getattr__をオーバーライドすると、通常の属性アクセス方法では見つからない属性にアクセスしたとき、代わりにユーザーが指定したフィールドの辞書から値が返されます。これにより、ユーザーが指定したフィールドには属性と同じ方法でアクセスできる一方、ユーザー入力によってクラスの属性が上書きされることを防げます。
Pythonライブラリを保護する
ソフトウェア開発の真っ只中では、セキュリティと利便性、柔軟性のバランスを取るのは難しいものです。一見無害に思えることでも、あなたやアプリケーションをさまざまなセキュリティリスクにさらす可能性があります。だからこそ、知識を身につけ、代替手段を知ることが重要です。ここでは、setattrの仕組み、Pythonライブラリでこれに頼る危険性、そしてMass Assignmentの脆弱性を防ぐいくつかの方法を解説しました。これらの知識を活用すれば、Pythonアプリケーションを攻撃やデータ侵害から守るための一歩を踏み出せます。詳しくは、ホームページにアクセスして無料アカウントを作成するか、デモをご予約ください。Snykがコード、コンテナ、依存関係、クラウドインフラストラクチャの保護にどう役立つかをご紹介します。
CTFを始めよう
オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。



