Despite the UI telling you that pw logins were disabled, they were not:
AUTHENTICATION_BACKENDS = (
'jodlplatform.backends.FaceAuthenticationBackend', 'django.contrib.auth.backends.ModelBackend')
Every login attempt that failed in FaceAuthenticationBackend was also handled by default django backend. The form parsed the pw parameter and passed it to authenticate:
def clean(self):
username = self.cleaned_data.get('username')
password = self.data.get('password')
[...]
if username is not None:
self.user_cache = authenticate(self.request, username=username, password=password, face_img=face_img)
[...]
The users' passwords were hashed with md5 without any salt, so bruteforcing all of their 8-char password (0-9, A-Z) was done in a few minutes with hashcat. Every team's ambassador (the user which submits flags) was visible in the about page (and their IDs mapped to the team IDs), so you could login into their accounts and fetch the flags.
You can fix this by disabling the pw login completely, but we restricted it to our test account only.
An image was considered to be ok if the probability was above 0.5, but the flagbot's images were similar enough so that we could increased the threshhold to 0.999. This eliminated a lot of image attacks, but unfortunately not all.