From b88fc06d3696055e1bad0782c70d43d39787b6e8 Mon Sep 17 00:00:00 2001 From: bobiqqq Date: Wed, 26 Aug 2026 13:15:58 +0300 Subject: [PATCH] =?UTF-8?q?tiktak:=20=D0=BD=D0=B5=20=D0=B2=D1=8B=D0=B4?= =?UTF-8?q?=D0=B0=D0=B2=D0=B0=D1=82=D1=8C=20=D0=B4=D0=BE=D1=81=D1=82=D1=83?= =?UTF-8?q?=D0=BF=20=D0=BA=20=D0=BD=D0=B5=D1=81=D1=83=D1=89=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D1=83=D1=8E=D1=89=D0=B5=D0=BC=D1=83=20=D0=B2=D0=B8?= =?UTF-8?q?=D0=B4=D0=B5=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handleAccess вызывал AddAccess не проверяя, что видео существует. Значит можно было пре-авторизоваться на ещё не созданные id (и на отрицательные): POST /access videoID= с форженным токеном возвращает 302, строка в access создаётся, и когда чекер создаст видео с этим id -- доступ уже есть. Главное: такая строка ПЕРЕЖИВАЕТ ротацию токенов. haveAccess() смотрит в таблицу access, а не на токен, поэтому переход на HMAC от заранее набитых записей не защищает. Проверено на 10.10.10.5: /access для id=top+3000 -> 302 /watch/63481. Чистка уже накопленного мусора (легитимных записей на несуществующие видео быть не может): DELETE a FROM access a LEFT JOIN video v ON v.id = a.video_id WHERE v.id IS NULL; Co-Authored-By: Claude Opus 5 --- services/tiktak/server/server.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/services/tiktak/server/server.go b/services/tiktak/server/server.go index b7f2e2d..8a55574 100644 --- a/services/tiktak/server/server.go +++ b/services/tiktak/server/server.go @@ -232,6 +232,19 @@ func (s *Server) handleAccess(c echo.Context) error { key := c.FormValue("token") if keygen.ValidateKey(key, vid) { ctx := context.Background() + + // AddAccess не проверял существование видео, поэтому можно было + // пре-авторизоваться на ещё не созданные id (и на отрицательные). + // Такая строка в access переживает любую последующую ротацию + // токенов: haveAccess() смотрит в таблицу, а не на токен. + v, err := s.db.GetVideo(ctx, vid) + if err != nil || v == nil { + return c.Render(http.StatusUnprocessableEntity, "access", AccessResponse{ + ErrorResponse: ErrorResponse{"no such video"}, + ID: vid, + }) + } + if err := s.db.AddAccess(ctx, vid, ac.GetId()); err != nil { return c.Render(http.StatusServiceUnavailable, "access", AccessResponse{ ErrorResponse: ErrorResponse{"try again later: " + err.Error()},