fix(anti-hijack): validate cookie_id against DB on every authed read
Closes the post-recovery re-attack window. Previously cookies were authenticated purely cryptographically — once a hijacker received a signed cookie for student_id=X, that cookie remained valid forever (until QUIZ_SECRET_KEY rotated), even after admin clear-student + legit re-claim issued a fresh cookie_id for X. Now /me, /event, and /ws/student all check that the cookie's cookie_id matches participants.cookie_id for the (sid, student_id). Mismatch -> 401 + Set-Cookie clearing for HTTP, ws.close(4001) for WS. The legit re-claim wins because admin clear_student deletes the row and the next join inserts the new student's cookie_id; the hijacker's cookie now fails the DB check on every subsequent request. Test: tests/test_anti_cheat.py::test_post_recovery_old_cookie_is_dead covers the full hijack -> clear -> re-claim -> hijacker-locked-out sequence end to end.
This commit is contained in:
16
app/room.py
16
app/room.py
@@ -189,6 +189,22 @@ class RoomManager:
|
||||
+ sum(len(clients) for clients in self.projector_clients.values())
|
||||
)
|
||||
|
||||
async def cookie_id_matches(self, sid: str, student_id: str, cookie_id: str) -> bool:
|
||||
"""Check the student's signed cookie_id against the DB participant
|
||||
row. Used to defend against the post-recovery re-attack: after
|
||||
admin clears a hijacked id and the legitimate student re-joins
|
||||
with a fresh cookie_id, the original hijacker's cookie is still
|
||||
cryptographically valid (the secret key is unchanged), but the
|
||||
DB cookie_id now belongs to the legit student. We reject any
|
||||
request whose cookie_id doesn't match the current row."""
|
||||
async with connect(self.settings.db_path) as db:
|
||||
cur = await db.execute(
|
||||
"SELECT cookie_id FROM participants WHERE sid = ? AND student_id = ?",
|
||||
(sid, student_id),
|
||||
)
|
||||
row = await cur.fetchone()
|
||||
return row is not None and row["cookie_id"] == cookie_id
|
||||
|
||||
async def add_participant(self, sid: str, student_id: str, name: str, cookie_id: str) -> None:
|
||||
"""First-claim-wins. Raises DuplicateStudentId if this student_id
|
||||
is already in the participants table for this sid (an attempt to
|
||||
|
||||
Reference in New Issue
Block a user