危険なピクルス-Pythonでの悪意のあるシリアル化

みなさんこんにちは



Panta rhei、そしお曎新されたコヌス「Python Web Developer」の立ち䞊げが近づいおいたすが、私たちは非垞に興味深く、あなたず共有したい資料をただ持っおいたす。



なぜ挬物が危険なのですか

これらの挬物は非垞に危険です。 どのように説明するのかさえ分かりたせん。 ただ信じお。 これは重芁ですよね
「爆発性障害」パンテラレ



オペコヌドに飛び蟌む前に、基本に぀いお話したしょう。 Python暙準ラむブラリには、オブゞェクトをシリアル化および非シリアル化するために䜿甚されるpickle 「 pickle 」たたは単に「保存」ず翻蚳されるず呌ばれるモゞュヌルがありたす。 これのみが、シリアラむれヌション/デシリアラむれヌションではなく、ピクルス化/アンピクルス化ず呌ばれたす文字通り-「保存/保存解陀」。







C ++でBoost Serializationを䜿甚した埌も悪倢に苊しんでいる人ずしお、私は保存が優れおいるず蚀うこずができたす。 あなたが圌女に䜕を投げおも、圌女はただ仕事を続けたす。 たた、組み蟌み型だけでなく、ほずんどの堎合、シリアル化キャニングメ゜ッドを蚘述する必芁なく、クラスをシリアル化できたす。 再垰的なデヌタ構造などのオブゞェクト同様のマヌシャリングモゞュヌルを䜿甚するずクラッシュを匕き起こすでも、問題はありたせん。



以䞋は、pickleモゞュヌルに慣れおいない人のための簡単な䟋です。



import pickle #      Python original = { 'a': 0, 'b': [1, 2, 3] } #     pickled = pickle.dumps(original) #      identical = pickle.loads(pickled)
      
      





ほずんどの堎合、これで十分です。 保存は本圓にクヌルです...しかし、深みのどこかに暗闇が隠されおいたす。



pickleモゞュヌルの最初の行の1぀はこう蚀いたす

泚意pickleモゞュヌルは、誀ったデヌタや悪意のあるデヌタから保護されおいたせん。 信頌できない、蚱可されおいない゜ヌスからのデヌタを再保存しないでください。



私はこの譊告を䜕床も読みたしたが、悪意のあるデヌタが䜕であるかずよく疑問に思いたした。 そしお最近、私はそれを芋぀ける時だず決めたした。 無駄ではありたせん。

悪意のあるデヌタを䜜成するための探求は、pickleプロトコルに぀いお倚くを孊び、保存をデバッグするためのクヌルな方法を発芋し、Python゜ヌスコヌドで倧胆なコメントをいく぀か芋぀けたした。 読み続けるず、同じ利点が埗られたすそしおすぐに、悪意のある保護ファむルも送信し始めるでしょう。 譊告技術的な詳现がありたすが、唯䞀の前提条件はPythonの基本的な知識です。 しかし、アセンブラの衚面的な知識は害になりたせん。



停のピクルス爆匟



私はpickleモゞュヌルのドキュメントを読むこずから始め、゚リヌトハッカヌになるためのヒントを芋぀けたいず思っお、次のような行に出䌚いたした。

pickletoolsモゞュヌルには、保存によっお生成されたデヌタフロヌを分析するためのツヌルが含たれおいたす。 pickletoolsの゜ヌスコヌドには、pickleプロトコルで䜿甚されるオペコヌドに関する広範なコメントが含たれおいたす。



オペコヌド pickleの実装が次のようになるずは思っおいたせんでした。



 def dumps(obj): return obj.__repr__() def loads(pickled): # :  pickle  ... return eval(pickled)
      
      





しかし、私は圌女が圌女自身の䜎レベル蚀語を定矩するこずも期埅しおいたせんでした。 幞いなこずに、行の2番目の郚分は真実を瀺しおいたす-pickletoolsモゞュヌルは、プロトコルがどのように機胜するかを理解するのに非垞に圹立ちたす。 さらに、コヌド内のコメントは非垞に面癜いこずがわかりたした。



たずえば、焊点を圓おる必芁があるプロトコルのバヌゞョンの問題を提起したす。 Python 3.6には合蚈5぀ありたす。 これらは0から4たでの番号が付けられおいたす。プロトコル0は、ドキュメントで「 読み取り可胜 」ず呌ばれ、 pickletools゜ヌスコヌドが远加情報を提䟛するため、明らかな遞択です。



ピクルオペコヌドは、物事の新しい方法が登堎しおもフェヌドするこずはありたせん。 PMのレパヌトリヌは時間ずずもに成長しおいたす...「Opscode bloating」は埮劙なヒントではなく、衰匱させる困難の原因です。



新しいプロトコルはそれぞれ、前のプロトコルのスヌパヌセットであるこずがわかりたした。 プロトコル0が「読み取り可胜」であるこずを考慮しなくおも呜什を逆コンパむルするため問題ではありたせん、可胜な限り少ないオペコヌドが含たれおいたす。 悪意のあるpickleファむルがどのように䜜成されるかを理解するこずが目暙である堎合、これは理想的です。



オペコヌドず混同しおも心配する必芁はありたせん。 ここでPythonに戻り、オペコヌドがPythonコヌドにどのように関連するかを詳しく説明したす。 オペコヌドのない単玔なPythonクラスを䜜成したす。



 class Bomb: def __init__(self, name): self.name = name def __getstate__(self): return self.name def __setstate__(self, state): self.name = state print(f'Bang! From, {self.name}.') bomb = Bomb('Evan')
      
      





__setstate __および__getstate __メ゜ッドは、クラスをシリアラむズおよびデシリアラむズするためにpickleモゞュヌルで䜿甚されたす。 デフォルトの実装は__dict__むンスタンスを単玔にシリアル化するだけなので、倚くの堎合、自分で定矩する必芁はありたせん。 ご芧のずおり、Bombオブゞェクトのデシリアラむズの瞬間にちょっずした驚きを隠すために、ここで盎接定矩したした。



逆シリアル化コヌドが動䜜するかどうかを確認しおください。 以䞋を䜿甚しお、オブゞェクトを保存および再保存できたす。



 import pickle pickled_bomb = pickle.dumps(bomb, protocol=0) unpickled_bomb = pickle.loads(pickled_bomb)
      
      





取埗するもの



 # -!  . Bang! From, Evan.
      
      





たさに蚈画通り 唯䞀の問題がありたす。Bombが定矩されおいないコンテキストでpickled_bomp行を逆シリアル化しようずしおも、䜕も機胜したせん。 代わりに、゚ラヌが衚瀺されたす。



 AttributeError: Can't get attribute 'Bomb' on <module '__main__'>
      
      





__setstate__()



的なコンテキストが悪意のある印刷匏を䜿甚しおコヌドに既にアクセスしおいる堎合にのみ、カスタムメ゜ッド__setstate__()



実行できるこずが__setstate__()



たす。 そしお、被害者が立ち䞊げたコヌドにすでにアクセスできるのに、なぜピクルスで悩むのですか 被害者が䜿甚する他の方法で悪意のあるコヌドを簡単に曞くこずができたす。 これは本圓です-私はそれを実蚌したかっただけです。



結局、Pytonがオブゞェクトの逆シリアル化メ゜ッドの保存バむトコヌドをサポヌトしおいるのではないかず疑うこずは無駄ではありたせん。 たずえば、マヌシャルモゞュヌルはメ゜ッドをシリアル化でき、倚くのpickleの代替手段 marshmallow 、 dill 、およびpyroも関数のシリアル化をサポヌトしおいたす。



ただし、pickleのドキュメントにある䞍吉な譊告は、それを意味するものではありたせん。 逆シリアル化の危険性を調べるには、もう少し深く掘り䞋げる必芁がありたす。



Pickleの逆コンパむル



保党が実際にどのように機胜するかを理解しようずする時です。 前のセクションのオブゞェクトpickled_bombを芋おみたしょう。



 b'ccopy_reg\n_reconstructor\np0\n(c__main__\nBomb\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\nVEvan\np5\nb.'
      
      





埅っおください...プロトコル0を䜿甚したしたか 「読み取り可胜」ですか



しかし、それは問題ありたせん。pickletoolsの゜ヌスコヌドには、 「pickleプロトコルで䜿甚されるオペコヌドに関する広範なコメント」がありたす。 問題を解決するのに圹立぀はずです

私はこれを詳现に文曞化するこずを切望しおいたす-すべおの特別なケヌスを芋぀けるためにピクルスコヌドを完党に読んでください。
-pickletools゜ヌスコヌドのコメント



なんおこった 䜕に適合したすか



冗談ですが、pickleツヌルの゜ヌスコヌドは本圓に玠晎らしいコメントです。 たた、ツヌル自䜓も同様に有甚です。 たずえば、pickletools.disずいうpickleを分解するメ゜ッドがありたす。 ピクルスをより理解しやすい蚀語に翻蚳するのに圹立ちたす。



pickled_bomb行を逆アセンブルするには、次を実行したす。



 import pickletools pickletools.dis(pickled_bomb)
      
      





   : 0: c GLOBAL 'copy_reg _reconstructor' 25: p PUT 0 28: ( MARK 29: c GLOBAL '__main__ Bomb' 44: p PUT 1 47: c GLOBAL '__builtin__ object' 67: p PUT 2 70: N NONE 71: t TUPLE (MARK at 28) 72: p PUT 3 75: R REDUCE 76: p PUT 4 79: V UNICODE 'Evan' 85: p PUT 5 88: b BUILD 89: . STOP highest protocol among opcodes = 0
      
      





x86 、 Dalvik 、 CLRなどの蚀語を扱っおいた堎合、䞊蚘のすべおがおなじみのように思えるかもしれたせん。 しかし、圌らがそれを持っおいなかったずしおも-それは問題ではありたせんが、私たちは段階的にそれを取りたす。 珟時点では、GLOBAL、PUT、およびMARKなどの芋出し語は、高氎準蚀語の関数のようにほずんど解釈されるオペコヌドおよび呜什であるこずを知るだけで十分です。 右偎はすべおこれらの関数の匕数であり、巊偎は元の「読み取り可胜な」行での暗号化方法を瀺しおいたす。



ただし、りォヌクスルヌを開始する前に、pickletoolsから別の䟿利なものを玹介したしょうpickletools.optimize。 このメ゜ッドは、未䜿甚のオペコヌドをピクルスから削陀したす。 出力は単玔化されおいたすが、同様のピクルスです。 次を実行するこずにより、pickled_bombの最適化されたバヌゞョンを解析できたす。



 pickled_bomb = pickletools.optimize(pickled_bomb) pickletools.dis(pickled_bomb)
      
      





そしお、䞀連の指瀺の簡略版を取埗したす。



  0: c GLOBAL 'copy_reg _reconstructor' 25: ( MARK 26: c GLOBAL '__main__ Bomb' 41: c GLOBAL '__builtin__ object' 61: N NONE 62: t TUPLE (MARK at 25) 63: R REDUCE 64: V UNICODE 'Evan' 70: b BUILD 71: . STOP highest protocol among opcodes = 0
      
      





これは、すべおのPUTオペコヌドが存圚しない堎合にのみオリゞナルず異なるこずに気付くかもしれたせん。 これにより、理解するための10の手順が残りたす。 すぐにそれらを個別に怜蚎し、Pythonコヌドを手動で「解析」したす。



保存䞭、オペコヌドは通垞Pickle MachinePMず呌ばれる゚ンティティによっお解釈されたす。 各ピクルは、コンパむルされたJavaコヌドがJava仮想マシンJVMで実行されるのず同様に、PMで実行されるプログラムです。 pickleコヌドを解析するには、PMの動䜜を理解する必芁がありたす。



PMには、デヌタを保存し、それらずやり取りするための2぀の領域がありたす。メモずスタックです。 メモは、長期保存甚に蚭蚈されおおり、敎数ずオブゞェクトを比范するPython蟞曞に䌌おいたす。 スタックは、倚くの操䜜が盞互䜜甚し、物を远加したり匕き出したりするPythonリストのようなものです。 これらのPythonデヌタ領域を次のように゚ミュレヌトできたす。



 #  / PM memo = {} # Stack PM,       stack = []
      
      





保存䞭、PMはpickleプログラムを読み取り、各呜什を順次実行したす。 STOPオペコヌドに到達するたびに終了したす。 スタックの䞀番䞊にあるオブゞェクトは、再保存の最終結果です。 ゚ミュレヌトされたメモずスタックリポゞトリを䜿甚しお、ピクルスをPythonに呜什ごずに倉換しおみたしょう。























ふう、完了です コヌドが特にPythonであるかどうかはわかりたせんが、PMを゚ミュレヌトしたす。 メモを䜿甚したこずがないこずに気付くかもしれたせん。 pickletools.optimizeで削陀されたすべおのPUTオペコヌドを芚えおいたすか 圌らはモモずやり取りしおいたかもしれたせんが、簡単な䟋ではこれは必芁ありたせんでした。



コヌドを単玔化しお、その䜜業を明確に瀺したしょう。 実際、デヌタのシャッフルに加えお、呜什1で_reconstructorをむンポヌトし、呜什7で_reconstructorを呌び出し、呜什9で__setstate __を呌び出すずいう3぀の操䜜のみが発生したす。デヌタのシャッフルを想像するず、Pythonの3行すべおを衚珟できたす。



 #  1,    `_reconstructor` from copyreg import _reconstructor #  7,  `_reconstructor`   unpickled_bomb = _reconstructor(cls=Bomb, base=object, state=None) #  9,  `__setstate__`   unpickled_bomb.__setstate__('Evan')
      
      





copyreg._reconstructor゜ヌスコヌドの内郚を芋るず、単にオブゞェクト.__ new __Bombを呌び出しおいるこずがわかりたす。 この知識を䜿甚しお、すべおを2行に簡略化できたす。



 unpickled_bomb = object.__new__(Bomb) unpickled_bomb.__setstate__('Evan')
      
      





おめでずうございたす、あなたはピクルスを逆コンパむルしたした



リアルピクルボム



私はピクルスの専門家ではありたせんが、悪意のあるピクルスの䜜成方法に぀いおはすでに説明できたす。 GLOBALオペコヌドを䜿甚しお任意の関数os.systemおよび__builtin __をむンポヌトできたす。Evalは適切な候補のようです。 そしお、REDUCEを䜿甚しお、任意の匕数で実行したす。 しかし、ただ...埅っお、それは䜕ですか



isinstancecallable、typeでない堎合、callregableがcopyregモゞュヌルのsafe_constructorsディクショナリに登録されおいる堎合、たたはcallableにtrue倀を持぀マゞックアトリビュヌト__safe_for_unpickling__がある堎合にのみ、REDUCEは宣誓したせん。 なぜこれが起こるのかはわかりたせんが、<winks>ずいう䞍満は十分にありたす。



応答しおりィンク。 pickletoolsのドキュメントは、蚱可されたcallableのみがREDUCEで実行できるこずを瀺唆しおいるようです。 しばらくの間、私は心配したしたが、「safe_constuctors」を怜玢するず、2003幎からPEP 307がすぐに芋぀かりたした。



Pythonの以前のバヌゞョンでは、保存解陀には個々の操䜜に察する「セキュリティチェック」があり、__ safe_for_unpickling__属性が1に等しいか、たたはグロヌバルレゞスタcopy_reg.safe_constructorsに登録するために「保存解陀」ずマヌクされおいない関数たたはコンストラクタヌの呌び出しを拒吊したした。



この関数は、誀った安心感を生み出したす。信頌できない゜ヌスからのピクルスの遞択が䞍芁なコヌドを匕き起こさないこずを蚌明するために必芁な広範なコヌド怜蚌を実行した人はいたせん。 実際、Python 2.2のpickle.pyモゞュヌルのバグにより、これらの予防措眮を簡単に回避できたす。



むンタヌネットを䜿甚する堎合、実装が完党に怜蚌されおいないプロトコルのセキュリティを信頌するよりも、プロトコルが安党でないこずを知った方がよいず固く信じおいたす。 䞀般的なプロトコルの高品質の実装でさえ、倚くの堎合゚ラヌを含んでいたす。 倚くの時間がないず、Pythonのpickle実装は保蚌できたせん。 したがっお、Python 2.3以降、すべおの保存解陀セキュリティチェックは公匏に陀倖され、譊告に眮き換えられたす。

è­Šå‘Š 信頌性の䜎い未怜蚌の゜ヌスからのデヌタを再床開かないでください。



こんにちは、暗闇、旧友 。 これがすべおの始たりです。



それがすべおであり、重芁な芁玠を芋぀けたした。そしお、私たちがやろうずしおいるこずから誀った安心感はありたせんでした。 爆匟を曞くこずから始めたしょう



 #       arbitrary python GLOBAL '__builtin__ eval' #      MARK #   Python,       UNICODE 'print("Bang! From, Evan.")' #    ,       REDUCE TUPLE #  `eval()`    Python    REDUCE #  STOP,   PM   STOP
      
      





これを実際のピクルスにするには、各オペコヌドを察応するASCIIコヌドに眮き換える必芁がありたすGLOBALのc、MARKのV、UNICODEのV、TUPLEのt、REDUCEのR、およびSTOPの堎合。これらは同じ倀であるこずに泚意しおください。 pickletools.disの出力のオペコヌドの巊偎に曞き蟌たれた匕数は、䜍眮ず改行制玄の組み合わせを考慮しお各オペコヌドの埌に​​分析されたす。各匕数は、察応するオペコヌドの盎埌たたは前の匕数の埌に配眮され、改行文字が芋぀かるたで。 次のように目の挬物コヌドが甚意されおいたす。



 c__builtin__ eval (Vprint("Bang! From, Evan.") tR.
      
      





最埌に、これを確認できたす。



 #   ! #  , ! pickled_bomb = b'c__builtin__\neval\n(Vprint("Bang! From, Evan.")\ntR.' pickle.loads(pickled_bomb)
      
      





III ...



 # -!  . Bang! From, Evan.
      
      





あなたは私を信じる理由がないこずを知っおいたすが、本圓にうたくいきたした。

誰もがevalに察しおより悪意のある議論を簡単に思い付くこずができるこずは簡単にわかりたす。 PMは、os.systemシステムコマンドを含む、Pythonコヌドが実行できるすべおの凊理を文字通り実行できたす。



すべおの良いこずが終わりたす



危険な挬物の䜜り方を孊ぶ぀もりでしたが、その過皋で誀っお挬物の仕組みを理解したした。 私は認める、私はこのピクルスマシンを掘り䞋げるのが奜きだった。 pickletoolsの゜ヌスコヌドは倧いに圹立ちたした。pickleプロトコルずPMの詳现に興味がある堎合はお勧めしたす。



終わり



い぀ものように、私たちはここで、たたはオヌプンデヌでむリダ・レベデフに個人的に尋ねるこずができる願いや質問を埅っおいたす。



All Articles