CCCamp CTF 2023 [web / Cybercrime Society Club Germany]

概要

loginとsignupがある、webサービス、adminの権限があると'date'コマンドを実行することができる flag.txtの中にflagがある、admin権限を取得し、flagの中身を見るコマンドを実行する必要がある。

まず、admin権限を取得していく

Writeup

    def is_admin(self, email):
        user = self.db.get(email)
        if user is None:
            return False
        
        return user["email"] == "admin@cscg.de" and user["userid"] > 90000000

admin権限になるためにuseridが90000000以上かつ、emailが「admin@cscg.de」となっている

admin@cscs.deのemailの取得

userdb = UserDB("userdb.json")
userdb.add_user("admin@cscg.de", 9_001_0001, str(uuid())) 

def api_create_account(data, user):

    if email == "admin@cscg.de":
        return error_msg("cant create admin")

    assert(len(groupid) == 3)
    assert(len(userid) == 4)
    userid = json.loads("1" + groupid + userid)

    if not check_activation_code(activation):
        return error_msg("Activation Code Wrong")
    print("activation passed")

    if userdb.add_user(email, userid, password):
        return success_msg("User Created")
    else:
        return error_msg("User creation failed")

def check_activation_code(activation_code):
    # no bruteforce
    time.sleep(20)
    if "{:0>4}".format(random.randint(0, 10000)) in activation_code:
        return True
    else:
        return False

ユーザ作成時にActivation_codeに0から10000のランダム値が含まれていないと、ユーザの作成が行われない。
activate_codeには文字数制限やチェックが入っていないので、0000~9999までのすべての値を結合したものをactivatecodeとすることで通過できる。

loginが成功すると、ユーザのemailアドレスの変更と削除ができる。

adminデータの削除

def api_delete_account(data, user):
    if user is None:
        return error_msg("not logged in")

    if data["data"]["email"] != user["email"]:
        return error_msg("Hey thats not your email!")

    if delete_accs(data["data"].values()):
        return success_msg("deleted account")

def delete_accs(emails):
    for email in emails:
        userdb.delete_user(email)
    return True

「if delete_accs(data["data"].values()):」.values()で値を指定しているため、{"data"{"email":"登録したユーザ"}, {"mail":"admin@cscg.de"}}とすることでadminのデータを消すことができる。

再度ユーザを作るり、ユーザの編集を行う。

def api_edit_account(data, user):
    if user is None:
        return error_msg("not logged in")
    
    new = data["data"]["email"]

    if userdb.change_user_mail(user["email"], new):
        return success_msg("Success")
    else:
        return error_msg("Fail")

edit_accountで「admin@cscg.de」にすることで、adminのemalを取得できた。
※同じ名前のemailを作れないため、一度adminのデータを削除する必要があった。

userid >90000000をクリアして完全にadmin権限を奪う

  assert(len(groupid) == 3)
  assert(len(userid) == 4)

    userid = json.loads("1" + groupid + userid)

素直に考えるとMAXの値は 19999999で当然False
テストしまくった結果、9e9で9*109となり、useridの値をinfにすることができた

これでadmin権限を取得できた。

flagを取りに行く!

admin権限によって、linuxコマンドが実行できるようになる

def api_admin(data, user):
    if user is None:
        return error_msg("Not logged in")
    is_admin = userdb.is_admin(user["email"])
    if not is_admin:
        return error_msg("User is not Admin")

    cmd = data["data"]["cmd"]
    # currently only "date" is supported
    if validate_command(cmd):
        out = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        return success_msg(out.stdout.decode())

def validate_command(string):
    return len(string) == 4 and string.index("date") == 0

素数が4であり、dateから始まる必要がある。
これは、配列を使うことで解決できる。

cmd = ["date","--help","help","-help"]
len(cmd)は4であり、dateから始まるので問題ない。
しかし使用できる、コマンドはdateのみなので有用なオプションがないか探す。
-fオプションでファイルを指定できる。
これでFlagゲット。