From 1f40efc60b93a0da3ef5c302e5bc017dc27adc03 Mon Sep 17 00:00:00 2001
From: Gusted <postmaster@gusted.xyz>
Date: Fri, 25 Oct 2024 08:18:26 +0200
Subject: [PATCH] fix(sec): use constant time check for internal token

(cherry picked from commit 53231bad611285b2c52c489a308cbdc8f6dce222)
---
 release-notes/5719.md       | 1 +
 routers/private/internal.go | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)
 create mode 100644 release-notes/5719.md

diff --git a/release-notes/5719.md b/release-notes/5719.md
new file mode 100644
index 0000000000..19a74825e4
--- /dev/null
+++ b/release-notes/5719.md
@@ -0,0 +1 @@
+Forgejo generates a token which is used to authenticate web endpoints that are only meant to be used internally, for instance when the SSH daemon is used to push a commit with Git. The verification of this token was not done in constant time and was susceptible to [timing attacks](https://en.wikipedia.org/wiki/Timing_attack). A pre-condition for such an attack is the precise measurements of the time for each operation. Since it requires observing the timing of network operations, the issue is mitigated when a Forgejo instance is accessed over the internet because the ISP introduce unpredictable random delays.
diff --git a/routers/private/internal.go b/routers/private/internal.go
index ede310113c..311f59b60e 100644
--- a/routers/private/internal.go
+++ b/routers/private/internal.go
@@ -5,6 +5,7 @@
 package private
 
 import (
+	"crypto/subtle"
 	"net/http"
 	"strings"
 
@@ -28,7 +29,7 @@ func CheckInternalToken(next http.Handler) http.Handler {
 			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
 			return
 		}
-		if len(fields) != 2 || fields[0] != "Bearer" || fields[1] != setting.InternalToken {
+		if len(fields) != 2 || fields[0] != "Bearer" || subtle.ConstantTimeCompare([]byte(fields[1]), []byte(setting.InternalToken)) == 0 {
 			log.Debug("Forbidden attempt to access internal url: Authorization header: %s", tokens)
 			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
 		} else {