Безопасность: fail-closed auth (H1), доверенный прокси для IP (H2), CSRF на /order (M2)
По результатам security-аудита (docs, run-1). Три верифицированные находки. H1 (HIGH) — fail-open аутентификация. loadUsers() при ошибке чтения/парсинга users.json молча возвращал пустой стор → authOpen() считал это первичной настройкой → после удаления first-run файла весь /admin отдавался без входа. Теперь: постоянный маркер configured (пишется при создании первого админа и бэкфиллится при загрузке валидного стора); authOpen() открыт только на реально свежей системе; при пустом/битом сторе на настроенной системе — 503 и блокировка до восстановления файла (authLockedOut). Тест TestAuthFailClosed (4 сценария). H2 (HIGH) — интернет-слой доверял клиентскому X-Forwarded-For/X-Real-IP, что позволяло обойти лимит входа по IP и травить аудит. Теперь заголовкам доверяем только от доверенного прокси (по умолчанию loopback; расширяется BJ_TRUSTED_PROXIES), из XFF берём правый элемент. Исправлено и в Go-модуле (modules/lk/security.go), и в PHP-ретрансляторе. Тесты TestClientIPTrust (Go) и прогон логики PHP. M2 (MEDIUM) — POST /order (поручение на списание бумаг) не проверял CSRF, в отличие от /login и /impersonate. Добавлен csrfOK (форма уже несла токен). Тест TestOrderCSRF. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
+8
-1
@@ -1082,7 +1082,14 @@ func (s *Server) buildHandler() http.Handler {
|
||||
"PickISIN": r.URL.Query().Get("isin"), "Today": time.Now().Format("02.01.2006")})
|
||||
return
|
||||
}
|
||||
_ = r.ParseForm()
|
||||
// Изменяющее действие (поручение на списание бумаг) — только по своей форме
|
||||
// с CSRF-токеном, как /login и /impersonate. Иначе чужая страница могла бы
|
||||
// отправить поручение от имени вошедшего клиента.
|
||||
if !csrfOK(r) {
|
||||
setFlashCookie(w, r, "Форма устарела, откройте поручение заново.")
|
||||
http.Redirect(w, r, "/order", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
var secs []map[string]any
|
||||
for _, isin := range r.Form["isin"] {
|
||||
q := strings.TrimSpace(r.FormValue("qty_" + isin))
|
||||
|
||||
Reference in New Issue
Block a user