Multiple vulnerabilities in CTFd version <= 3.7.4

Multiple vulnerabilities in CTFd versions <= 3.7.4 allows a remote attacker who acquired user’s activation or password reset link (e.g. from browser history) to hijack victim’s account. Other vulnerability allows the user to pass access control and change an already set bracket.

Product name: CTFd

Website: https://ctfd.io

Product description: CTFd is a Capture The Flag framework focusing on ease of use and customizability. It comes with everything you need to run a CTF and it’s easy to customize with plugins and themes.




[Vulnerability #1] User can change the bracket without administrator

CVE

CVE-2024-11716

Affected versions

CTFd (https://ctfd.io) versions <= 3.7.4

CVSSv4

5.3 Medium (CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N)

Description

CTFd offers scoreboard bracket function where users can be assigned to a bracket and are scoring is grouped by brackets. The function allows the user to pick a bracket once at start. It seems the logic’s intention is to not allow the user to change the bracket when it was already assigned:

195  @pre_load
196  def validate_bracket_id(self, data):
197      bracket_id = data.get("bracket_id")
198      if bracket_id is None:
199          return
200  
201      current_user = get_current_user()
202      if is_admin():
203          bracket = Brackets.query.filter_by(id=bracket_id, type="users").first()
204          if bracket is None:
205              ValidationError(
206                  "Please provide a valid bracket id", field_names=["bracket_id"]
207              )
208      else:
209          if (
210              current_user.bracket_id == int(bracket_id)
211              or current_user.bracket_id is None
212          ):
213              bracket = Brackets.query.filter_by(id=bracket_id, type="users").first()
214              if bracket is None:
215                  ValidationError(
216                      "Please provide a valid bracket id", field_names=["bracket_id"]
217                  )
218          else:
219              raise ValidationError(
220                  "Please contact an admin to change your bracket",
221                  field_names=["bracket_id"],
222              )

Please note in lines 210-211 the code verifies if user has changed the bracket_id and if so raises an error stating that only administrator can do so.

This validation suggests the system treats this as a security control and does not allow to switch brackets. Some setups/deployments might assume trust to this function.

The user can however bypass the check by:

  1. First saving user details with bracket_id=null - GUI blocks this but API allows according to line 198. E.g.

    curl 'http://ctfd/api/v1/users/me' --H 'Content-Type: application/json' \
         -H 'Content-Type: application/json' -H "CSRF-Token: $CSRF" \
          --data-raw '{"name":"test","email":"t@t.pl","bracket_id":null,"fields":[]}'
    
  2. Second saving again with the proper changed bracket_id - this will be allowed according to line 211. This can be done normally in GUI.

PoC example

Some contests can use the scoreboard bracket system to group users into different prize pools. Having the possibility to change the bracket might end up with cheating and getting the prize.

Solution

It should not be possible to change an already set bracket to null and this should be verified in the API (backend) OR (if the security control is simply wrong) there should be no ValidationError in order not to confuse CTFd implementers.

Fixed in: https://github.com/CTFd/CTFd/pull/2636




[Vulnerability #2] Multiple vulnerabilities in token handling

CVE

CVE-2024-11717

Affected versions

CTFd (https://ctfd.io) versions <= 3.7.4

CVSSv4

6.3 Medium (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N)

Description

[Vulnerability #2.1] Account activation and password reset tokens can be interchanged

CTFd uses the “itsdangerous” python package for creating activation and password reset tokens. In both cases it is done by signing user’s e-mail address with timestamp with the system’s secret_key.

The generated token is directly used in URL sent to the user over e-mail. During token validation, in both activation and password reset, the system is checking if the HMAC signature is correct and if the token was not created more than 1800s (30min) ago. Then the system verifies if the signed value matches any user’s e-mail address. The matched user is being either activated or setting new password is being permitted.

The tokens for both use cases have exactly the same construction and can be used interchangeably. Particularly, activation token can be used in reset password function which is pretty dangerous especially when the user registers and the token was used in GET request what means it can be stored in many places - browser’s history, proxy access logs, and so on. An attacker gaining access to such token within the expiration time can use it to gain control of the victim’s account.

Connected with the vulnerability #3 the impact is even more visible as the token can be reused many times and cannot be revoked by the user until it expires.

[Vulnerability #2.2] Account activation and password reset tokens are not single-use

The described above mechanism of token generation is stateless. This means tokens are not single-use and can be used withing the expiration timeout (30min) multiple times.

Tokens sent in URL can be stored in many places - e-mail, browser’s history, proxy access logs and so on. An attacker gaining access to such token within the expiration time can use it to gain control of the victim’s account.

[Vulnerability #2.3] Email in token sent in GET requests

The tokens for resetting password and activating account have base64 encoded email address of the user in plain text. They are opened via GET requests which means can be stored in many places and thus may leak user’s email address.

PoC example

High school and university students are encouraged to participate in a CTF. They read this info on classes and are registering to the CTF using a university/school computer (e.g. during classes).

The attack does not depend on email access:

  1. Victim registers to CTF.
  2. Victim logs into e-mail account and clicks the link.
  3. Victim logs out of CTFd and e-mail account.
  4. Attacker looks up browser history, takes the token and base64 decodes email address (vuln #4).
  5. Attacker within 30 minutes takes the activation token and reuses it to reset victim’s password (vuln #2 and vuln #3).

Solution

To fix all 3 vulnerabilities it would be good to change the tokens to random numbers (no email) stored in Redis cache and use keys distinguishing the action type (activation vs reset password). After usage the token can be removed from Redis thus making the tokens single-use.

Fixed in: https://github.com/CTFd/CTFd/pull/2679




Timeline




Vendor response

Special thanks to Kevin Chung (ColdHeat) from CTFd for fast response and fixes.




References




Credits

Author: Blazej Adamczyk (br0x) https://sploit.tech/

Team: Efigo https://efigo.pl/




tags: CTF CTFd CVE-2024-11716 CVE-2024-11717