はじめに
これは RICORA Advent Calendar 2022 9日目の記事です。
以前CTFに参加した際に解いたPythonのエクスプロイトの問題で, privateなメソッドをクラスの外から呼ぶ解法で解いたので, 調べて直してまとめました.
Pythonのclassにおけるprivateの振る舞い
まず, 次のようなコードで通常の振る舞いを確認します.
class Test:
a = 1
__a = 10
def show(self):
print(self.a)
def __private_show(self):
print(self.__a)
普通の関数および変数は問題なく呼び出すことができます.
test = Test()
print(test.a)
test.show()
# 1
# 1
アンダーバーを2つつけたフィールドおよび関数はprivateになるので, このような参照は行うことができません.
print(test.__a)
test.__private_show()
# Traceback (most recent call last):
# File "c:\Users\***\Documents\py\advent\a.py", line 17, in <module>
# print(test.__a)
# AttributeError: 'Test' object has no attribute '__a'
特殊属性, 特殊メソッドについて
これは __main__
や __name__
などのことです. この2つはグローバルから参照することができたりしますが, classにおいても存在してます.
先ほどのTestクラスで試してみると
print(Test.__dict__.keys())
# dict_keys(['__module__', 'a', '_Test__a', 'show', '_Test__private_show', '__dict__', '__weakref__', '__doc__'])
このように, classの持つ名前空間にアクセスすることができます.
privateへのアクセス
これを使えば次のようにprivateなパラメータに対してもアクセスできてしまいます.
print(Test.__dict__["a"])
# 1
print(Test.__dict__["_Test__a"])
# 10
Test.__dict__["show"](Test)
# 1
Test.__dict__["_Test__private_show"](Test)
# 10
この方法を用いたCTFの問題では, eval
に任意の文字を送ることができたので, この例のようにアクセスし, flagを入手しました.
追記
アクセスする際に参照した__dict__
ですが, 公式ドキュメントによると
カスタムクラス型は通常、クラス定義 (クラス定義 参照) で生成されます。クラスは辞書オブジェクトで実装された名前空間を持っています。クラス属性の参照は、この辞書に対する探索 (lookup) に翻訳されます。例えば、 C.x は C.__dict__["x"] に翻訳されます (ただし、属性参照の意味を変えられる幾つかのフックがあります)。
ということなので, 次のように呼ぶこともできます.
print(Test._Test__a);
# 10
Test._Test__private_show(Test)
# 10
インスタンスからのアクセス
インスタンスからも同様にアクセスすることができます.
print(Test().show())
# 1
object.__new__(Test).show()
# 1
Test()._Test__private_show()
# 10
object.__new__(Test)._Test__private_show()
# 10
おわりに
最近pythonの仕様を突いたエクスプロイトの問題をよく見かけるので, 備忘録としてこの記事を書きました.
このジャンルの問題は毎回頭を悩ませる反面, 非常に面白くもあるのでCTFで見かけた際には積極的に解きに行こうと思います.
参考サイト
編集履歴
2022/12/09 __dict__
について追記, インスタンスからの呼び出しについて訂正