Skip to main content

setattrの危険性:PythonにおけるMass Assignmentの脆弱性を防ぐ

著者
Headshot of Jack Hair

Jack Hair

blog feature pypi spoof

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と出力されます。

class User():
	def __init__(self, admin=False):
    		self.admin = admin

	def is_admin(self):
    		return "You are admin!" if self.admin else "Permission denied"

def parse_input(usr_in):
	return dict([tuple(kv.split("=", 1)) for kv in usr_in.split(",") if "=" in kv])

def create_user(info):
	user = User()
	for key, value in info.items():
    	setattr(user, key, value)
	return user

def main():
	user = create_user(parse_input(str(input())))
	if hasattr(user, "first_name") and hasattr(user, "last_name"):
    		print(f"{user.first_name} {user.last_name}: {user.is_admin()}")

if __name__ == "__main__":
	main()

捕捉されない例外によってプログラムをクラッシュさせるには、入力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が発生し、これを捕捉して処理することで、前述の攻撃を防ぎます。

from dataclasses import dataclass

@dataclass
class UserDTO():
	first_name: str
	last_name: str

class User():
	def __init__(self, dto, admin=False):
    		self.first_name = dto.first_name
    		self.last_name = dto.last_name
    		self.admin = admin

	def is_admin(self):
    		return "You are admin!" if self.admin else "Permission denied"

def parse_input(usr_in):
	return dict([tuple(kv.split("=", 1)) for kv in usr_in.split(",") if "=" in kv])

def create_user(info):
	try:
    		return User(UserDTO(**info))
	except TypeError:
    		return None

def main():
	user = create_user(parse_input(str(input())))
	if user:
    		print(f"{user.first_name} {user.last_name}: {user.is_admin()}")

if __name__ == "__main__":
	main()

ユーザー入力の検証も、Mass Assignmentの脆弱性を防ぐ方法の一つです。常に、禁止データを確認するよりも、承認済みデータのリストを使って検証する方が適切です。以下のコードではsetattrを引き続き使用しながら、属性名が許可された属性のリストに含まれているかを確認します。

class User():
	allowed_fields = ["first_name", "last_name"]

	def __init__(self, admin=False):
    		self.admin = admin

	def is_admin(self):
    		return "You are admin!" if self.admin else "Permission denied"

def parse_input(usr_in):
	return dict([tuple(kv.split("=", 1)) for kv in usr_in.split(",") if "=" in kv])

def create_user(info):
	user = User()
	for key, value in info.items():
    		if key in User.allowed_fields:
        			setattr(user, key, value)
    		else:
        			return None
	return user

def main():
	user = create_user(parse_input(str(input())))
	if user:
    		print(f"{user.first_name} {user.last_name}: {user.is_admin()}")

if __name__ == "__main__":
	main()

プログラムが想定するユーザー入力は、常に明示するのが最善です。ただし、ユーザー入力の処理に柔軟性が必要な場合は、setattr関数以外の方法もあります。Python標準ライブラリのcollectionsモジュールには、便利なクラスが2つ用意されています。collections.UserDictとcollections.abc.MutableMappingです。これらのクラスを継承することで、キーと値のマッピングに機能を追加できます。キーではなく属性を使って値にアクセスする必要がある場合は、クラスに__getattr__メソッドを実装します。以下の例では、Userクラスが辞書のように動作し、ユーザーが指定した任意のフィールドを保存できます。__getattr__をオーバーライドすると、通常の属性アクセス方法では見つからない属性にアクセスしたとき、代わりにユーザーが指定したフィールドの辞書から値が返されます。これにより、ユーザーが指定したフィールドには属性と同じ方法でアクセスできる一方、ユーザー入力によってクラスの属性が上書きされることを防げます。

from collections import UserDict

class User(UserDict):
	def __init__(self, info, admin=False):
    		self.admin = admin
    		super().__init__(info)

	def is_admin(self):
    		return "You are admin!" if self.admin else "Permission denied"

	def __getattr__(self, name):
    	if name in self:
     		   	return self[name]
    		else:
        		raise AttributeError("'User' object has no attribute '{}'".format(name))

def parse_input(usr_in):
	return dict([tuple(kv.split("=", 1)) for kv in usr_in.split(",") if "=" in kv])

def create_user(info):
	return User(info)

def main():
	user = create_user(parse_input(str(input())))

	if "first_name" in user and "last_name" in user:
    		print(f"{user.first_name} {user.last_name}: {user.is_admin()}")

if __name__ == "__main__":
	main()

Pythonライブラリを保護する

ソフトウェア開発の真っ只中では、セキュリティと利便性、柔軟性のバランスを取るのは難しいものです。一見無害に思えることでも、あなたやアプリケーションをさまざまなセキュリティリスクにさらす可能性があります。だからこそ、知識を身につけ、代替手段を知ることが重要です。ここでは、setattrの仕組み、Pythonライブラリでこれに頼る危険性、そしてMass Assignmentの脆弱性を防ぐいくつかの方法を解説しました。これらの知識を活用すれば、Pythonアプリケーションを攻撃やデータ侵害から守るための一歩を踏み出せます。詳しくは、ホームページにアクセスして無料アカウントを作成するか、デモをご予約ください。Snykがコード、コンテナ、依存関係、クラウドインフラストラクチャの保護にどう役立つかをご紹介します。

CTFを始めよう

オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。

続きを読む

illustration hero ai
Blog

PHPが重大な未認証RCEの脆弱性2件を修正(CVE-2026-109618、CVE-2026-109619):今すぐアップデート

PHPのリクエスト処理に重大な脆弱性が2件あり、未認証のリモートコード実行につながるおそれがあります。修正版にアップグレードするか、旧バージョンにはベンダー提供のバックポートを適用してください。

blog feature ai lilac
Blog

エージェント型修正で脆弱性バックログをプルリクエストに変える

Evo Remediation Agentがオープンプレビューで利用可能になり、Evo Agentic AppSecに依存関係の自動解決機能が加わりました。AppSecチームは継続的な修正キャンペーンを実施できるようになりました

AI Background min crop
Blog

AIが、あなたが受け入れてきたあらゆるリスクを一気に顕在化させる:AI Security Panel: Unfilteredから得た7つの洞察

AIによって攻撃はより安価かつ高速になり、組織がすでに抱えているリスクが露呈しています。4人のセキュリティリーダーが、機械の速度に対応する防御と迅速な修復が今不可欠である理由を語ります。