OWASP / OWASP/SecurityShepherd
Timezone bug: suspended user can authenticate on non-UTC JVM deployments
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 1.5k
- Forks
- 515
- Avg merge
- 3h 46m
- Merged PRs (30d)
- 1
Description
Severity
Security-relevant. A suspended user can still authenticate if the Tomcat JVM's default timezone differs from the database server's timezone. The suspension check silently passes even though the database has the user marked as suspended.
Root cause
Getter.authUser reads suspendedUntil via JDBC:
suspendedUntil = userResult.getTimestamp(8);
JDBC's getTimestamp(int) (no Calendar parameter) interprets the SQL DATETIME value in the JVM's default time zone.
If the JVM is Europe/Dublin (UTC+1 in summer) and the DB is UTC:
Setter.suspendUser(userId, 10)calls stored proc that storessuspendedUntil = NOW() + 10 minutesin UTC.authUserreads that DATETIME and JDBC interprets it as IST time, producing aTimestampwhose epoch-ms is 1 hour earlier than the actual stored UTC instant.- The check
suspendedUntil.after(currentTime)returnsfalsebecause the misinterpreted timestamp is in the past. - User is permitted to log in despite being suspended.
Same bug affects every DATETIME column the app compares against System.currentTimeMillis():
users.suspendedUntil(auth flow)settings.startTime/lockTime/endTime(CTF schedule gates)
Why CI doesn't catch this
GitHub Actions runners default to UTC. With JVM and DB both in UTC, the misinterpretation is a no-op and the tests pass. Local developer machines (Europe/Dublin in this case, but any non-UTC zone) reproduce the bug.
Diagnosis
Direct JDBC test showed:
JVM TZ: Europe/Dublin
DB NOW() as Timestamp: 2026-05-11 22:05:19.0 (epoch ms=1778533519000) <- DB UTC time read as IST
DB UTC_TS() as Timestamp: 2026-05-11 22:05:19.0 (epoch ms=1778533519000)
Java now: 2026-05-11 23:05:19.085 (epoch ms=1778537119085) <- actual current epoch
Both DB readings are exactly 1 hour earlier than the actual current epoch. With -Duser.timezone=UTC the readings align.
MariaDB Connector/J 3.x parameters tried (none fixed the read-side interpretation):
serverTimezone=UTC(MySQL Connector/J parameter, not honored)connectionTimeZone=UTC/SERVER/disableforceConnectionTimeZoneToSession=truepreserveInstants=false
This is by design in JDBC: Timestamp is an instant in the JVM's TZ unless you pass a Calendar.
Workaround (already applied for tests)
Pin tests to UTC in pom.xml via <argLine>-Duser.timezone=UTC</argLine> on both surefire and failsafe configs. This matches what CI does implicitly and unblocks local IT runs on non-UTC machines. It does not fix the production bug.
Real fix (this issue)
Pick one of:
Option A: Use UTC Calendar everywhere we read SQL timestamps
private static final Calendar UTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
...
suspendedUntil = userResult.getTimestamp(8, UTC);
Apply to every rs.getTimestamp(...) site in Getter.java and any other DAO. Doesn't affect storage, only how Java interprets reads.
Pros: targeted, no JVM-wide impact, no deployment requirement.
Cons: every JDBC call site needs updating; easy to miss new sites.
Option B: Migrate stored DATETIMEs to TIMESTAMP (which is timezone-aware)
MariaDB TIMESTAMP columns are stored as UTC and converted on read to the session timezone. With SET time_zone = '+00:00' on every JDBC session, reads come back in UTC.
Pros: handles all time columns consistently.
Cons: requires schema migration + session-init SQL on the pool config.
Option C: Mandate UTC JVMs in deployment
Document that Tomcat must run with -Duser.timezone=UTC. Add a Database.java startup check that warns if TimeZone.getDefault() != UTC.
Pros: simplest code change.
Cons: pushes the fix to ops; doesn't actually fix the code; vulnerable to misconfiguration.
Recommendation
Option A for correctness in this PR; consider Option B as a longer-term DAO refactor (#815).
Acceptance criteria
- All
rs.getTimestamp(...)calls inGetter.java(and any other DAO) pass a UTCCalendar, OR equivalent fix that produces correct epoch-ms regardless of JVM TZ. - Targeted ITs (
SetterIT#testSuspendUser,GetterAuthIT#suspendedUserIsRejected,GetterIT#testSSOAuthSuspended) pass on a non-UTC JVM without the-Duser.timezone=UTCworkaround. - Manual test: deploy Tomcat with
-Duser.timezone=US/Pacific, suspend a user, attempt login — login is rejected. - The
<argLine>-Duser.timezone=UTC</argLine>workaround inpom.xmlcan be reverted once the proper fix lands.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by auditing the rs.getTimestamp(...) calls in Getter.java and any other DAO, then review the UTC test configuration in pom.xml. Run SetterIT#testSuspendUser, GetterAuthIT#suspendedUserIsRejected, and GetterIT#testSSOAuthSuspended under a non-UTC JVM. Done means timestamp comparisons use UTC-independent reads, the targeted tests pass without the timezone workaround, and suspended users are rejected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, mariadb
- Domain
- authentication, backend, databases, security, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100