thumbnail
记一次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(),请。

#coding=utf-8
from flask import Flask, request
import random

wrong_msg = ["嘤嘤嘤,听不懂吖QAQ~", "呜呜呜~咱好笨~", "咱看不懂这些呀……", "咿呀,好像出错了……", "这超出窝的理解范围了QwQ", "好难~咱听不懂~"]


def keyword_filter(keyword, msg):
    for i in keyword:
        if i not in msg:
            return False
    return True


def py_filter(msg):
    for keyword in ["class", "import", "eval", "exec", "sys", "globals", "_", "builtins", "getattr", "pow", "",
                    "&",]:
        if keyword_filter(keyword, msg):
            return False
    if "**" in msg:
        return False

    return True


def do_python(msg):
    try:
        if py_filter(msg):
            temp = eval(msg)
        else:
            return "Hacking for fun~"

        return str(temp)
    except:
        return random.choice(wrong_msg)


app = Flask(__name__)


@app.route("/submit", methods=["GET"])
def submit():
    data = request.args.get("text")
    print(data)
    return do_python(data)


if __name__ == '__main__':
    app.run(host="0.0.0.0")

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表达式

"{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参考资料

  1. Linux的/proc/self/学习: https://blog.csdn.net/cjdgg/article/details/119860355
  2. -——已搬运———-Linux的/proc/self/学习 ++ CTF例题: https://blog.csdn.net/Zero_Adam/article/details/114853022
  3. 渗透系列之flask框架开启debug模式漏洞分析: https://cloud.tencent.com/developer/article/1726330
上一篇
下一篇