记一次Flask后端被渗透的经历
0x00写在前面
2021-11-04中午,在flask文档的帮助下,我成功在一小时内搭建起一个带有沙盒的可以执行部分python语句的后端脚本,并第一次将项目部署在docker上。NekoBot新功能+1,在某十几个群开放了此功能,并特别标注为菜单的第-1项,开始期待几个群聊里做渗透的师傅们对其进行攻击。
接下来的20多天里,如此明显的功能几乎没有人使用,这些人几乎全他妈的都在用Bot在看色图、犯病。不过好在有些人注意到了,其中有几位也是打CTF的师傅,可惜他们几乎都在瞎搞()这期间有个南信大的朋友,对bot进行了一些列攻击,可惜这些方式都在我的预判之内,并没成功。但是单纯的部分绕过都做到了,因为真的很简单()
2021-11-27凌晨,在持续几个小时的尝试下,这个后端被巨佬plusls(我也是从前小绿草的腿腿acdxvfsvd大仙贝那得知这位巨佬的)读取了源码,读取到了环境信息,最终利用flask的拒绝服务打了下来(我超我怎么不知道flask还有这个后门)。并在几个小时后发现了最后某几个关键字的绕过方法,基本就能get shell了。
在写waf的时候我预先留了个open方法,这就能让所有人对任意文件进行读操作,还有dir()啥的也没过滤(要是过滤了很多东西也没法用了x);当然还有一些然后以我浅薄的认知,封掉了几个可以利用os模块进行RCE的方法和关键词。我想看看,巨佬是怎么在这些阴间过滤的条件下成功get shell。很幸运,plusls的攻击过程十分精彩,让我学到许多,这波熬夜值了。记录一下记录一下.jpg
(虽然说最后plusls师傅似乎发现了非常不错的绕过方式,但是我被他打掉的脚本不敢重启了,万一他手里有逃出docker的0day呢(害怕.jpg))
0x01文件源码
这么屑的源码有公开的必要吗(恼),直接open('/proc/self/cwd/app/app.py', 'r').read()
,请。
1 | #coding=utf-8 |
0x02文件源码读取原理和/proc
我们都知道可以通过/proc/$pid/来获取指定进程的信息,例如内存映射、CPU绑定信息等等。如果某个进程想要获取本进程的系统信息,就可以通过进程的pid来访问/proc/$pid/目录。但是这个方法还需要获取进程pid,在fork、daemon等情况下pid还可能发生变化。为了更方便的获取本进程的信息,linux提供了/proc/self/目录,这个目录比较独特,不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。进程可以通过访问/proc/self/目录来获取自己的系统信息,而不用每次都获取pid。
Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
还有的是一些以数字命名的目录,他们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在/proc下,以进程的PID号为目录名,他们是读取进程信息的接口。而self目录则是读取进程本身的信息接口,是一个link
巨佬在做了一些常见的信息获取方式后,也试了一些关键字,然后就直接上手/proc/self/
这个目录了(不愧是bin巨佬)
读取/proc/self/maps可以得到当前进程的内存映射关系,通过读该文件的内容可以得到内存代码段基址。
/proc/self/mem是进程的内存内容,通过修改该文件相当于直接修改当前进程的内存。
(同样的,我们也可以通过写入mem文件来直接写入内存,例如直接修改代码段,放入我们的shellcode,从而在程序流程执行到这一步时执行shellcode来拿shell)
覆写got表我只在一些pwn题见过,python的PWN相关我一无所知(甚至没有我对pyc文件的逆向了解的多,乐),感觉非常难非常硬核,感觉现在以我的智商可能难以掌握()
巨佬一直在尝试compile,并且试图向/proc/self/mem
写入巨大多垃圾数据,应该是要搞堆栈溢出啥的,但好像没有什么太好的反馈(当然其他还有一些操作咱也看不懂QAQ)
说回读取文件目录的方法,一开始我看巨佬的操作我以为是通过/proc/self/exe
啥的(毕竟c的话,确实可以用readlink这个函数),但是佬用的方法事利用format函数和八进制绕过,最后采取了lambda表达式
1 | "{0.__globals__}".format(lambda x:x) |
不仅地址泄露了,函数什么的都泄露了(地址泄露了open也能用这些不是迟早的事吗x)
0x03Flask框架问题
debug模式
Flask在生产环境中开启debug模式是一件非常危险的事,主要有3点原因:
1、会泄露当前报错页面的源码,可供审计挖掘其他漏洞
2、会泄露Web应用的绝对路径,及Python解释器的路径(可以配合写文件漏洞向指定目录的文件内写入构造好的恶意代码,利用方式可以参考安全客的这篇文章:文件解压之过 Python中的代码执行)
3、debug页面中包含Python的交互式shell,可以执行任意Python代码
但是,进入这个Python shell是需要输入一个PIN码的。PIN码的生成和几个与机器以及脚本的参数有关,比较难;当然,Debug模式要是没开启PIN码验证的话,是可以直接进入shell的
request.environ
在Flask的官方文档中是这样介绍request的:对于 Web 应用,与客户端发送给服务器的数据交互至关重要。在 Flask 中由全局的 request 对象来提供这些信息。
从Flask模块导入request:from flask import request
request的属性众多,其中的environ,官方文档给出的描述是:WSGI隐含的环境配置。
好啊
werkzeug.server.shutdown
request.environ字典中一个名为 shutdown_server 的方法名分配的键为 werkzeug.server.shutdown
注射request.environ[‘werkzeug.server.shutdown’]()会造成拒绝服务。
捏麻麻滴,我是真的之前不知道flask的这些细节😅😅😅
我真的操了,最无语的是,request.environ['werkzeug.server.shutdown']()
这玩意在我的过滤规则下根本不用绕过😅。
😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅
0x04参考资料
- Linux的/proc/self/学习: https://blog.csdn.net/cjdgg/article/details/119860355
- -—-已搬运——-Linux的/proc/self/学习 ++ CTF例题: https://blog.csdn.net/Zero_Adam/article/details/114853022
- 渗透系列之flask框架开启debug模式漏洞分析: https://cloud.tencent.com/developer/article/1726330