def log_and_create_flag( self, ip: r8.util.THasIP, user: Optional[str] = None, *, max_submissions: int = 1, flag: Optional[str] = None, challenge: Optional[str] = None, ) -> str: """ Create a new flag that can be redeemed for this challenge and log its creation. If the challenge is currently inactive, `__flag__{challenge inactive}` will be returned instead. If flag creation should not be logged (e.g. because it's done by the challenge automatically on startup), use :func:`r8.util.create_flag` directly. Args: ip: IP address which caused this flag to be created. Used for logging only. user: User who caused this flag to be created. Used for logging only. challenge: If given, override the challenge for which this flag is valid. """ if not self.active: self.log(ip, "flag-inactive", uid=user) return "__flag__{challenge inactive}" if not challenge: challenge = self.id flag = r8.util.create_flag(challenge, max_submissions, flag) r8.log(ip, "flag-create", flag, uid=user, cid=challenge) return flag
def log(self, ip: r8.util.THasIP, type: str, data: Optional[str] = None, *, uid: Optional[str] = None) -> None: """ Log an event for the current challenge. See :func:`r8.log`. """ r8.log(ip, type, data, uid=uid, cid=self.id)
async def get_challenges(user: str, request: web.Request): """Get the current challenge state.""" r8.log(request, "get-challenges", request.headers.get("User-Agent"), uid=user) challenges = await r8.util.get_challenges(user) return web.json_response({ "user": user, "team": r8.util.get_team(user), "challenges": challenges, })
async def register(request: web.Request): """very very simply self-registration functionality that abuses teams for nicknames.""" if not r8.settings.get("register", False): return web.HTTPForbidden() logindata = await request.json() try: user = logindata["username"] password = logindata["password"] nickname = logindata["nickname"] except KeyError: r8.log(request, "register-invalid", "incomplete request") return web.HTTPBadRequest(reason="All fields are required.") with r8.db: user_exists = r8.db.execute("SELECT 1 FROM users WHERE uid = ?", (user, )).fetchone() team_exists = r8.db.execute("SELECT 1 FROM teams WHERE tid = ?", (nickname, )).fetchone() if user_exists: r8.log(request, "register-invalid", "username exists") return web.HTTPBadRequest( reason="There already exists an account with this email.") if team_exists: r8.log(request, "register-invalid", "team exists") return web.HTTPBadRequest( reason="There already exists a team with that name.") with r8.db: r8.db.execute("INSERT INTO users(uid, password) VALUES (?,?)", (user, r8.util.hash_password(password))) r8.db.execute("INSERT INTO teams(uid, tid) VALUES (?,?)", (user, nickname)) r8.log(request, "register-success", uid=user) return await login(request)
async def handle_challenge_request(user: str, request: web.Request): try: inst = r8.challenges[request.match_info["cid"]] except KeyError: return web.HTTPBadRequest(reason="Unknown challenge.") if request.method == "GET": resp = await inst.handle_get_request(user, request) if isinstance(resp, str): resp = web.json_response({"message": resp}) else: path = (request.match_info["path"] + " ").lstrip() text = await request.text() if request.content_type == 'application/x-www-form-urlencoded': text = urllib.parse.unquote(text) data = path + text # We want this to appear before any challenge-specific logging... rowid = r8.log(request, "handle-request", data, uid=user, cid=inst.id) try: resp = await inst.handle_post_request(user, request) if isinstance(resp, str): resp = web.json_response({"message": resp}) with r8.db: r8.db.execute( """UPDATE events SET data = ? WHERE ROWID = ?""", (f"{data} -> {resp.status} {resp.reason}", rowid)) except Exception as e: with r8.db: r8.db.execute("""UPDATE events SET data = ? WHERE ROWID = ?""", (f"{data} -> {e}", rowid)) raise return resp
async def login(request: web.Request): logindata = await request.json() try: user = logindata["username"] password = logindata["password"] except KeyError: r8.log(request, "login-invalid") return web.HTTPBadRequest(reason="username or password missing.") with r8.db: ok = r8.db.execute( "SELECT password FROM users WHERE uid = ?", (user,) ).fetchone() try: if not ok: raise ValueError() r8.util.verify_hash(ok[0], password) r8.log(request, "login-success", uid=user) token = r8.util.auth_sign.sign(user.encode()).decode() is_secure = not r8.settings["origin"].startswith("http://") resp = web.json_response({}) resp.set_cookie( "token", token, path="/", max_age=31536000, samesite="strict", httponly=True, secure=is_secure, ) return resp except (argon2.exceptions.VerificationError, ValueError): r8.log(request, "login-fail", user, uid=user if ok else None) return web.HTTPUnauthorized( reason="Invalid credentials." )
async def log_request(request: web.Request, handler): if isinstance(challenge.log_web_requests, bool): should_log = challenge.log_web_requests else: should_log = challenge.log_web_requests(request) if not should_log: return await handler(request) req_str = f"{request.method} {request.path_qs}" # We want this to appear before any challenge-specific logging... rowid = r8.log(request, "handle-request", req_str, cid=challenge.id) resp_str: str = "" try: resp = await handler(request) resp_str = f"{resp.status} {resp.reason}" except Exception as e: resp_str = f"{e}" raise else: return resp finally: req_text: str = "" try: if request._post is not None or request.content_type in ( "application/x-www-form-urlencoded", "multipart/form-data"): req_data = await request.post() req_text = "&".join(f"{k}={v}" for k, v in req_data.items()) else: req_text = await request.text() req_text = req_text[:1024] except Exception as e: req_text = req_text or f"{e}" req_str = f"{req_str} {req_text}".rstrip() with r8.db: r8.db.execute("""UPDATE events SET data = ? WHERE ROWID = ?""", (f"{req_str} -> {resp_str}", rowid))
def submit_flag(flag: str, user: str, ip: THasIP, force: bool = False) -> str: """ Returns: the challenge id Raises: ValueError, if there is an input error. """ with r8.db: user_exists = r8.db.execute( """ SELECT 1 FROM users WHERE uid = ? """, (user, )).fetchone() if not user_exists: r8.log(ip, "flag-err-unknown", flag) raise ValueError("Unknown user.") flag, cid = (r8.db.execute( """ SELECT fid, cid FROM flags NATURAL INNER JOIN challenges WHERE fid = ? OR fid = ? """, (flag, correct_flag(flag))).fetchone() or [flag, None]) if not cid: r8.log(ip, "flag-err-unknown", flag, uid=user) raise ValueError("Unknown Flag ¯\\_(ツ)_/¯") is_active = r8.db.execute( """ SELECT 1 FROM challenges WHERE cid = ? AND datetime('now') BETWEEN t_start AND t_stop """, (cid, )).fetchone() if not is_active and not force: r8.log(ip, "flag-err-inactive", flag, uid=user, cid=cid) raise ValueError("Challenge is not active.") is_already_submitted = r8.db.execute( """ SELECT COUNT(*) FROM submissions NATURAL INNER JOIN flags NATURAL INNER JOIN challenges WHERE cid = ? AND ( uid = ? OR challenges.team = 1 AND submissions.uid IN (SELECT uid FROM teams WHERE tid = (SELECT tid FROM teams WHERE uid = ?)) ) """, (cid, user, user)).fetchone()[0] if is_already_submitted: r8.log(ip, "flag-err-solved", flag, uid=user, cid=cid) raise ValueError("Challenge already solved.") is_oversubscribed = r8.db.execute( """ SELECT 1 FROM flags WHERE fid = ? AND (SELECT COUNT(*) FROM submissions WHERE flags.fid = submissions.fid) >= max_submissions """, (flag, )).fetchone() if is_oversubscribed and not force: r8.log(ip, "flag-err-used", flag, uid=user, cid=cid) raise ValueError("Flag already used too often.") r8.log(ip, "flag-submit", flag, uid=user, cid=cid) r8.db.execute( """ INSERT INTO submissions (uid, fid) VALUES (?, ?) """, (user, flag)) on_submit.send(user=user, cid=cid) return cid