mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-20 18:11:00 +00:00
fix(security): require a 2FA code to replace the stored TOTP secret
The confirmation gate in updateSetting only covered the true -> false transition, so a settings save that kept twoFactorEnable=true while carrying a non-blank twoFactorToken silently rebound the authenticator. preserveRedactedSecrets restores the stored secret only when the submitted one is blank, so a non-blank value went straight through without any branch asking for a code. Not reachable pre-auth or cross-site (CSRFMiddleware rejects unsafe methods without the session token), but it matters after a session hijack or with an admin API token, which sets api_authed and short-circuits the CSRF check: the attacker gains persistence and locks the legitimate operator out of their own authenticator. Now a code is required whenever 2FA is currently on and the submitted secret differs from the stored one. Enabling from off is untouched, as no code exists yet to verify, and a blank secret still means "unchanged", so the panel's normal save path is unaffected. Reported by @n0ctal (GHSA-xqqw-jqqv-99h6).
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
@@ -130,10 +131,16 @@ func (a *SettingController) updateSetting(c *gin.Context) {
|
||||
oldTgToken, _ := a.settingService.GetTgBotToken()
|
||||
oldTgChatId, _ := a.settingService.GetTgBotChatId()
|
||||
oldTgAPIServer, _ := a.settingService.GetTgBotAPIServer()
|
||||
if twoFactorErr == nil && oldTwoFactor && !allSetting.TwoFactorEnable {
|
||||
if err := a.settingService.VerifyTwoFactorCode(form.TwoFactorCode); err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
if twoFactorErr == nil && oldTwoFactor {
|
||||
// Rebinding the authenticator is the same class of change as turning 2FA
|
||||
// off, so both need a current code. Blank still means "unchanged".
|
||||
submittedToken := strings.TrimSpace(allSetting.TwoFactorToken)
|
||||
storedToken, _ := a.settingService.GetTwoFactorToken()
|
||||
if !allSetting.TwoFactorEnable || (submittedToken != "" && submittedToken != storedToken) {
|
||||
if err := a.settingService.VerifyTwoFactorCode(form.TwoFactorCode); err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err := a.settingService.UpdateAllSetting(allSetting, service.SecretClears{
|
||||
|
||||
Reference in New Issue
Block a user