魔理沙借书之旅v1

161次阅读
没有评论

魔理沙借书之旅v1

魔理沙的借书之旅

Pyjail逃逸+无回显处理

STEP 1: 分析源码:
下载题目附带的marisa.zip,发现有如下内容:
魔理沙借书之旅v1

   可以发现本题的运行环境为Docker,其中dockerfile是docker的配置文件。从中我们可以知道,python的运行版本是PY3.8(这就意味着有些方法我们用不了,后文会讲到),并且还有很关键的一点,就是当docker启动的时候,flag会从/app/flag移动到 /flag,至于文件夹里面提供的那个flag只是个假flag,但是提示了真flag可能存在的位置。

   继续分析源码marias.py,可以发现是一个基于flask搭建的沙箱环境,其中的limited\_builtins限制了我们可以在沙箱内使用的函数,my\_audit\_checker里面的blacklist则提供了一份黑名单,意思是在沙箱环境中,这些方法会被禁用:  

魔理沙借书之旅v1

   继续往下分析,发现safe\_exec 函数中存在关于unicode的过滤,会把输入的 Python 代码里所有 Unicode 转义符实际展开,之后检查代码语法树,交给 SandboxVisitor 验证是否安全。这就意味着,在接下来我们构造payload的时候,使用Unicode绕过就可能不是那么好绕了。

   继续往下分析,随后的代码中,通过调用子进程,对输入程序的运行时间进行了限制(timeout默认为1秒),是为了防止执行恶意代码导致进程卡死:  

魔理沙借书之旅v1

   随后程序进入了flask结构部分,需要重点关注的在exec视图函数下面:  

魔理沙借书之旅v1

这里使用了post方法向flask服务器传参,参数名为code。我们可以先用类似apifox的工具先进行测试。

至此。程序大体分析结束。

STEP 2: 理清攻击思路

   从上述的代码中,我们可以得知,在该沙箱环境中,常见的方法(例如 import、os等都被禁用)。但是我们要完成的任务其实很简单,就是在受限条件下,完成对/flag的读取,并想办法让他回显到前端获取。这时候就得想一想,有什么其他方法间接的调用呢这些方法呢?

   其实这类问题有相似的方案,在之前的SSTI题目中我们利用过一个思路:一些方法我们直接调用不了,但是其实很多python的内建类下,存在这些方法,我们可以试着尝试绕过,并用这样的手段执行文件读取命令。

STEP 3: 开始进攻

   首先我们可以先了解一下,在基类object下面的子类到底有什么,打开apifox,传参:print(“”.\_\_class\_\_.\_\_mro\_\_[-1].\_\_subclasses\_\_())  (如果你看不明白,可以在文末复习,有专题讲解)。于是我们得到:![](https://capoo.me/wp-content/uploads/2025/10/clip_image009-20250927190352-lggrr8r-1.png)

   将得到的结果复制到一个你用的习惯的编辑器,并选择替换,将所有的 , 都替换为,\\n  (记得勾选使用正则表达式)。随后查找一下与       class无关的行,并将其删去,这样就方便我们更好的观察到底有什么子类可用,并且更快速的获取ID,更清晰。

   使用基类绕过,不过于以下几种方式(这里我偷个懒,直接赋值HELLO CTF的图):

魔理沙借书之旅v1

图中的几个方法,我们一个一个来尝试:
1:eval exec

   由于之前在黑名单里面看到了builtins.eval,builtins.exec。这就说明通过调用内建函数来使用eval和exec的方法在本题完全不适用,排除。

2: os模块

   黑名单里没有出现,也许可以尝试一下调用os模块。我们在列表里面发现了直接有 \<class 'os.\_wrap\_close'\>这个类名直接在 os 命名空间下,ID为132,可说明 os 模块已经被加载。但是,由于我们得到的是os.open,需要使用如下格式:os.open(path, flags, mode\=0o777),但是,由于我们并不清楚flag的文件描述符,如果获取需要使用open(这个又被明确禁止),综上,此路不通。

   但是,os模块下面的system函数经过测试发现可以调用​ ~~(别问我为什么,这块我也不知道,反正乱撞装出来的,正在看这个wp如果知道可以教教我QAQ)~~ ​,我们可以尝试构造一个最简单的payload:  
   print(''.\_\_class\_\_.\_\_mro\_\_[-1].\_\_subclasses\_\_()[132].\_\_init\_\_.\_\_globals\_\_[&apos;system&apos;](“whoami”))

   我们可以用这个来测试是否可用。如果可用的话,会返回0。那为什么不是用户名呢?因为system函数的返回值只是状态码。而不是一个具体类似“root”的内容。也就是说,我们没法通过这种方式直接获得回显。

   但是换个思路,既然只能返回执行错误与否。我们能不能这样操作:写一个payload,逐字符读取,并于你键盘上的所有ASCII字符进行匹配判断,如果判断上了就返回true,错误就返回false。对应到system的返回值就是0和127的区别。这个思路有点想sql盲注,适用于在直接读取不到回显的时候使用。

       于是我们构造如下payload:  

print(”.__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__[‘system’]((‘char=$(head -c 1 /flag | tail -c 1); [ "$char" = "V" ]’)))

   这个就是在判断,flag文件中的第一个字符,和字符V是否匹配的上,如果正确,就返回0,错误就返回127。既然payload构造好了,下一步就是批量尝试+组装结果。

   打开你的BurpSuite。向[http://靶场地址/exec](http://靶场地址/exec)发post包,使用Intruder  

魔理沙借书之旅v1

千万注意取消URL编码字符,不然会把你传进去payload进行url编码,这样会导致一些字符无法被匹配。

然后得到了攻击结果,查找返回结果为0的值(数据包长度174),随后进行组装,就能够得到正确的flag。
魔理沙借书之旅v1

魔理沙借书之旅v1

   以上思路从时间上来说有点复杂,但是确实能拿到flag,是没有办法的办法。

3: popen函数

   subprocess.Popen在黑名单里直接被封杀,但是观察到在482行存在: \<class 'multiprocessing.popen\_fork.Popen'\> ,但是muliprocessing在payload中实例化很麻烦。我们依然可以用这个来调用os,方法于上方类似,payload如下:  

print(”.__class__.__mro__[-1].__subclasses__()[482].__init__.__globals__[‘os’].system("cat /flag"))

在这里还是需要解决回显的问题。

4: importlib类

5: linecache类 上面四种思路其实都一样

6: subprogress.popen: 已被blacklist ban

7: FileLoader 适用于python3.8以上,版本不对

预期解法:

flask的默认静态目录,里面的文件是可以直接通过host/static/filename访问的

正文完
 0
评论(没有评论)