"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
ensure gotwebd login token uniqueness
To:
gameoftrees@openbsd.org
Date:
Mon, 22 Dec 2025 14:38:43 +0100

Download raw body.

Thread
gotwebd login links requested within the same second are not unique.
Which means users (including anonymous) can deduce the occurance of
server-side clock ticks once per second by requesting login links
repeatedly and watch the login token changing.

I don't know if this is a problem in practice but this side-channel
can be closed with little effort by prepending random data to the
login token which gets mixed into the hash before the useful data is
mixed in.

ok?

M  gotwebd/login.c  |  10+  5-

1 file changed, 10 insertions(+), 5 deletions(-)

commit - f8c15ec8a9ddce2f08959da8a558e2f50b8738b8
commit + 012f34f998f2f6bad8853afdd0f6a3a63e582be9
blob - 9504381f3d7a57fcce332e523404ead0eb3ebe74
blob + d9f42f6aec8a16bc3ef3a406f2fc3e7d2b6ac426
--- gotwebd/login.c
+++ gotwebd/login.c
@@ -62,7 +62,7 @@ static char login_token_secret[32];
 /*
  * The token format is:
  *
- *    "v1\0"[issued at/64bit][expire/64bit][uid/64bit][host]"\0"
+ *    "v2\0"[random/64bit][issued at/64bit][expire/64bit][uid/64bit][host]"\0"
  *
  * Padded with additional \0 to a length divisible by 4, and then
  * followed by the HMAC-SHA256 of it, all encoded in base64.
@@ -75,7 +75,7 @@ login_check_token(uid_t *euid, char **hostname,
     const char *purpose)
 {
 	time_t	 now;
-	uint64_t issued, expire, uid;
+	uint64_t random, issued, expire, uid;
 	uint8_t *data;
 	int	 len;
 	char	 hmac[32], exp[32];
@@ -112,7 +112,7 @@ login_check_token(uid_t *euid, char **hostname,
 		return -1;
 	}
 
-	if (memcmp(data, "v1", 3) != 0) {
+	if (memcmp(data, "v2", 3) != 0) {
 		log_warnx("unknown %s token format version", purpose);
 		free(data);
 		return -1;
@@ -140,6 +140,9 @@ login_check_token(uid_t *euid, char **hostname,
 		return -1;
 	}
 
+	memcpy(&random, data + used, sizeof(random));
+	used += sizeof(random);
+
 	memcpy(&issued, data + used, sizeof(issued));
 	used += sizeof(issued);
 
@@ -193,7 +196,7 @@ login_gen_token(uint64_t uid, const char *hostname, ti
 	FILE		*fp;
 	char		*tok;
 	time_t		 now;
-	uint64_t	 issued, expire;
+	uint64_t	 random, issued, expire;
 	size_t		 siz, hlen, pad;
 	unsigned int	 hmaclen;	/* openssl... */
 
@@ -205,6 +208,7 @@ login_gen_token(uint64_t uid, const char *hostname, ti
 		fatalx("%s token secret length mismatch", purpose);
 
 	now = time(NULL);
+	arc4random_buf(&random, sizeof(random));
 	issued = (uint64_t)now;
 	expire = issued + validity;
 
@@ -215,7 +219,8 @@ login_gen_token(uint64_t uid, const char *hostname, ti
 	/* include NUL */
 	hlen = strlen(hostname) + 1;
 
-	if (fwrite("v1", 1, 3, fp) != 3 ||
+	if (fwrite("v2", 1, 3, fp) != 3 ||
+	    fwrite(&random, 1, 8, fp) != 8 ||
 	    fwrite(&issued, 1, 8, fp) != 8 ||
 	    fwrite(&expire, 1, 8, fp) != 8 ||
 	    fwrite(&uid, 1, 8, fp) != 8 ||