内容简介:不同的模板引擎,根据不同的解析方式相应的也是存在不同的利用方法。正常而言,出于安全考虑,模板引擎基本上都是拥有沙盒、命名空间的,代码的解析执行都是发生在有限的沙盒里面,因此,沙盒逃逸也成为 SSTI 不可或缺的存在。Tornado 中模板渲染函数在有两个
原理
不同的模板引擎,根据不同的解析方式相应的也是存在不同的利用方法。
正常而言,出于安全考虑,模板引擎基本上都是拥有沙盒、命名空间的,代码的解析执行都是发生在有限的沙盒里面,因此,沙盒逃逸也成为 SSTI 不可或缺的存在。
Python Web 模板引擎
- [x] Jinja2
- [x] Tornado.template
- [ ] Django.template
- ...
SSTI in Tornado
Tornado 中模板渲染函数在有两个
- render
- render_string
tornado/web.py:
class RequestHandler(object): .... def render(self, template_name, **kwargs): ... html = self.render_string(template_name, **kwargs) ... return self.finish(html) def render_string(self, template_name, **kwargs): template_path = self.get_template_path() ... with RequestHandler._template_loader_lock: if template_path not in RequestHandler._template_loaders: loader = self.create_template_loader(template_path) RequestHandler._template_loaders[template_path] = loader else: loader = RequestHandler._template_loaders[template_path] t = loader.load(template_name) namespace = self.get_template_namespace() namespace.update(kwargs) return t.generate(**namespace) def get_template_namespace(self): namespace = dict( handler=self, request=self.request, current_user=self.current_user, locale=self.locale, _=self.locale.translate, pgettext=self.locale.pgettext, static_url=self.static_url, xsrf_form_html=self.xsrf_form_html, reverse_url=self.reverse_url ) namespace.update(self.ui) return namespace
render_string:通过模板文件名加载模板,然后更新模板引擎中的命名空间,添加一些全局函数或其他对象,然后生成并返回渲染好的 html内容
render:依次调用 render_string
及相关渲染函数生成的内容,最后调用 finish 直接输出给客户端。
我们跟进模板引擎相关类看看其中的实现。
tornado/template.py
class Template(object): ... def generate(self, **kwargs): namespace = { "escape": escape.xhtml_escape, "xhtml_escape": escape.xhtml_escape, "url_escape": escape.url_escape, "json_encode": escape.json_encode, "squeeze": escape.squeeze, "linkify": escape.linkify, "datetime": datetime, "_tt_utf8": escape.utf8, # for internal use "_tt_string_types": (unicode_type, bytes), "__name__": self.name.replace('.', '_'), "__loader__": ObjectDict(get_source=lambda name: self.code), } namespace.update(self.namespace) namespace.update(kwargs) exec_in(self.compiled, namespace) execute = namespace["_tt_execute"] linecache.clearcache() return execute()
在上面的代码中,我们很容易看出命名空间namespace中有哪些变量、函数的存在。其中, handler 是一个神奇的存在。
tornado/web.py:
class RequestHandler(object): .... def __init__(self, application, request, **kwargs): super(RequestHandler, self).__init__() self.application = application self.request = request
在 RequestHandler
类的构造函数中,可以看到 application
的赋值。
tornado/web.py:
class Application(ReversibleRouter): .... def __init__(self, handlers=None, default_host=None, transforms=None, **settings): ... self.wildcard_router = _ApplicationRouter(self, handlers) self.default_router = _ApplicationRouter(self, [ Rule(AnyMatches(), self.wildcard_router) ]) class _ApplicationRouter(ReversibleRuleRouter): def __init__(self, application, rules=None): assert isinstance(application, Application) self.application = application super(_ApplicationRouter, self).__init__(rules)
因此,通过 handler.application
即可访问整个Tornado。
简单而言通过 {{handler.application.settings}}
或者 {{handler.settings}}
就可获得 settings
中的 cookie_secret
。
例题: 护网杯 2018 WEB (1) easy_tornado
另外,跟进 exec_in
中也有新发现。
tornado/util.py:
def exec_in(code, glob, loc=None): # type: (Any, Dict[str, Any], Optional[Mapping[str, Any]]) -> Any if isinstance(code, basestring_type): # exec(string) inherits the caller's future imports; compile # the string first to prevent that. code = compile(code, '<string>', 'exec', dont_inherit=True) exec(code, glob, loc)
这里用到了 compile 和 exec
SSTI in Flask
Flask 中模板渲染函数也是有两个
- render_template
- render_template_string
Flask使用的是 Jinja2 模板引擎
flask/templating.py
def _default_template_ctx_processor(): """Injects `request`,`session` and `g`.""" reqctx = _request_ctx_stack.top appctx = _app_ctx_stack.top rv = {} if appctx is not None: rv['g'] = appctx.g if reqctx is not None: rv['request'] = reqctx.request rv['session'] = reqctx.session return rv def _render(template, context, app): before_render_template.send(app, template=template, context=context) rv = template.render(context) template_rendered.send(app, template=template, context=context) return rv def render_template(template_name_or_list, **context): ctx = _app_ctx_stack.top ctx.app.update_template_context(context) return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list), context, ctx.app) def render_template_string(source, **context): ctx = _app_ctx_stack.top ctx.app.update_template_context(context) return _render(ctx.app.jinja_env.from_string(source), context, ctx.app)
render_template:通过模板文件加载内容并进行渲染
render_template_string:直接通过模板字符串进行渲染
这上下文、栈啥的看的有点懵,也不深入了。(有兴趣自行了解)
接着,我们看看 Flask 是怎么加载 Jinja2 的。 app.jinja_env
flask/app.py
class Flask(_PackageBoundObject): ... jinja_environment = Environment ... jinja_options = ImmutableDict( extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_'] ) ... @locked_cached_property def jinja_env(self): return self.create_jinja_environment() ... def create_jinja_environment(self): options = dict(self.jinja_options) rv = self.jinja_environment(self, **options) rv.globals.update( url_for=url_for, get_flashed_messages=get_flashed_messages, config=self.config, request=request, session=session, g=g ) rv.filters['tojson'] = json.tojson_filter return rv
这里我们可以看见 jinja_environment
中有6个全局变量,也就是说在模板引擎的解析环境中可以访问这6个对象。
例题 TokyoWesterns CTF 4th 2018 shrine
接下来,我们跟进 Jinja2 的代码里看看还有什么有意思的东西。
jinja2/environment.py
class Environment(object): ... def _generate(self, source, name, filename, defer_init=False): return generate(source, self, name, filename, defer_init=defer_init, optimized=self.optimized) def _compile(self, source, filename): return compile(source, filename, 'exec') @internalcode def compile(self, source, name=None, filename=None, raw=False, defer_init=False): source_hint = None try: if isinstance(source, string_types): source_hint = source source = self._parse(source, name, filename) source = self._generate(source, name, filename, defer_init=defer_init) if raw: return source if filename is None: filename = '<template>' else: filename = encode_filename(filename) return self._compile(source, filename) except TemplateSyntaxError: exc_info = sys.exc_info() self.handle_exception(exc_info, source_hint=source_hint) class Template(object): ... def render(self, *args, **kwargs): vars = dict(*args, **kwargs) try: return concat(self.root_render_func(self.new_context(vars))) except Exception: exc_info = sys.exc_info() return self.environment.handle_exception(exc_info, True) ...
这里也是通过 compile 对模板进行编译的
jinja2/parser.py
_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print', 'macro', 'include', 'from', 'import', 'set', 'with', 'autoescape']) _compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq']) _math_nodes = { 'add': nodes.Add, 'sub': nodes.Sub, 'mul': nodes.Mul, 'div': nodes.Div, 'floordiv': nodes.FloorDiv, 'mod': nodes.Mod, } ... class Parser(object): def parse_statement(self): ... try: if token.value in _statement_keywords: return getattr(self, 'parse_' + self.stream.current.value)() ... def parse_print(self): node = nodes.Output(lineno=next(self.stream).lineno) node.nodes = [] while self.stream.current.type != 'block_end': if node.nodes: self.stream.expect('comma') node.nodes.append(self.parse_expression()) return node
这里面, print
是个好东西,某些场景,限制了 {{
和 }}
的使用,只能使用 {%
和 %}
。这种清空,一般的想法是利用 if
来进行逻辑盲注,但是 {% print somedata %}
可以直接输出。
例题:网鼎杯 CTF 2018 第三场 Web mmmmy (暂无环境)
jinja2/defaults.py
DEFAULT_NAMESPACE = { 'range': range_type, 'dict': dict, 'lipsum': generate_lorem_ipsum, 'cycler': Cycler, 'joiner': Joiner, 'namespace': Namespace }
默认的命名空间里面还有一些奇奇怪怪的对象存在的。
在探索测试的过程中发现,其实你随便输入一个字符串都是有用的。
比如 {{vvv}}
,一般情况你会发现页面空白啥的没有,但是加点东西就是新世界 {{vvv.__class__}}
。Jinja2对不存在的对象有一个特殊的定义 Undefined
类`
<class 'jinja2.runtime.Undefined'>`。
jinja2/runtime.py
@implements_to_string class Undefined(object): ...
通过 {{ vvv.__class__.__init__.__globals__ }}
又能够搞事情了。
jinja2/filters.py
FILTERS = { 'abs': abs, 'attr': do_attr, 'batch': do_batch, 'capitalize': do_capitalize, 'center': do_center, 'count': len, 'd': do_default, 'default': do_default, 'dictsort': do_dictsort, 'e': escape, 'escape': escape, 'filesizeformat': do_filesizeformat, 'first': do_first, 'float': do_float, 'forceescape': do_forceescape, 'format': do_format, 'groupby': do_groupby, 'indent': do_indent, 'int': do_int, 'join': do_join, 'last': do_last, 'length': len, 'list': do_list, 'lower': do_lower, 'map': do_map, 'min': do_min, 'max': do_max, 'pprint': do_pprint, 'random': do_random, 'reject': do_reject, 'rejectattr': do_rejectattr, 'replace': do_replace, 'reverse': do_reverse, 'round': do_round, 'safe': do_mark_safe, 'select': do_select, 'selectattr': do_selectattr, 'slice': do_slice, 'sort': do_sort, 'string': soft_unicode, 'striptags': do_striptags, 'sum': do_sum, 'title': do_title, 'trim': do_trim, 'truncate': do_truncate, 'unique': do_unique, 'upper': do_upper, 'urlencode': do_urlencode, 'urlize': do_urlize, 'wordcount': do_wordcount, 'wordwrap': do_wordwrap, 'xmlattr': do_xmlattr, 'tojson': do_tojson, }
这些过滤器有些时候还是可以用到的,用法 {{ somedata | filter }}
, {{url_for.__globals__.current_app.config|safe}}
结束语
本文到这里就告一段落了,主要收获就是模板引擎命名空间或全局变量中的各种对象和函数。另外,其实还有很多地方没深入研究,大家有兴趣不妨翻翻源码找找有意思的事物。
标题有个 (一) ,算是给自己挖个坑,至于后续能不能填上就另说啦。。。。emmmmmmm
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Smashing Book
Jacob Gube、Dmitry Fadeev、Chris Spooner、Darius A Monsef IV、Alessandro Cattaneo、Steven Snell、David Leggett、Andrew Maier、Kayla Knight、Yves Peters、René Schmidt、Smashing Magazine editorial team、Vitaly Friedman、Sven Lennartz / 2009 / $ 29.90 / € 23.90
The Smashing Book is a printed book about best practices in modern Web design. The book shares technical tips and best practices on coding, usability and optimization and explores how to create succes......一起来看看 《The Smashing Book》 这本书的介绍吧!