Skip to content

fix: use UTC calendar for JDBC timestamp reads#843

Open
Gautam-aman wants to merge 14 commits into
OWASP:devfrom
Gautam-aman:fix/timezone-auth-bypass
Open

fix: use UTC calendar for JDBC timestamp reads#843
Gautam-aman wants to merge 14 commits into
OWASP:devfrom
Gautam-aman:fix/timezone-auth-bypass

Conversation

@Gautam-aman

Copy link
Copy Markdown

Fixes timezone-dependent authentication bypass caused by JDBC getTimestamp() interpreting SQL DATETIME values using the JVM default timezone.

On non-UTC JVM deployments, suspended users could authenticate because suspendedUntil timestamps were interpreted incorrectly and compared against the wrong epoch time.

This PR standardizes JDBC timestamp reads using a shared UTC Calendar.

Changes:
Added shared UTC JDBC calendar utility

Suspended users are now correctly rejected during authentication regardless of JVM timezone.

SeanDuggan
SeanDuggan previously approved these changes May 30, 2026

@SeanDuggan SeanDuggan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix looks good, minor issue with spotless violation which has been resolved.

@SeanDuggan SeanDuggan requested a review from ismisepaul May 30, 2026 13:18
@SeanDuggan SeanDuggan assigned ismisepaul and unassigned SeanDuggan May 30, 2026

@ismisepaul ismisepaul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice fix. The DbTime.UTC approach is correct and both getTimestamp (authUser + authUserSSO) are the only ones in the codebase, so the suspension-bypass bug is properly closed.

There is an open issue #841 which also looks at addressing both this, and CTF schedule gates. However, they're different mechanisms so let's get this merged in and that can be addressed in another pr (keeps this clean)

One thing before merge: this needs a regression test. CI runs on UTC, so this bug is invisible to it and can silently come back.

Suggestion: A unit test that puts the JVM in a non-UTC zone e.g. TimeZone.setDefault(US/Pacific), and assert the suspension timestamp still reads as the correct instant. So if anyone later drops the UTC calendar, CI fails immediately instead of the bug silently returning in production.

You can create it under src/test/java/utils/DbTimeTest.java

@Gautam-aman

Copy link
Copy Markdown
Author

Added a regression test under utils/DbTimeTest that runs with a non-UTC JVM timezone and verifies the UTC calendar behavior remains correct. All tests pass locally.

@ismisepaul

Copy link
Copy Markdown
Member

Thanks @Gautam-aman

One thing before we merge: as written the test doesn't quite guard the bug we care about. It asserts DbTime.UTC.getTimeZone().getID() is "UTC" which only confirms the constant was built with a UTC zone. We want to lock in the behaviour because if someone later drops the DbTime.UTC argument from the read path, this test would still pass as CI won't catch it since it runs in UTC.

Suggestion: instead of checking the DbTime.UTC constant directly, test the actual read. You can do this without a database by using Mockito (already a test dependency) to create a fake ResultSet (a stand-in object that returns whatever value you tell it to)

So something like

  • Set the JVM's default timezone to something non-UTC (e.g. US/Pacific) at the start of the test, and restore the original in a finally block - like you're already doing.
  • Create the fake ResultSet and tell it that when getTimestamp(col, DbTime.UTC) is called, it should return a timestamp for a known moment in time.
  • Call that method and check the result matches the exact moment you expect (compare the epoch-millisecond values, since that's the absolute instant independent of any timezone).

The key is that the expected value is worked out as UTC. If the read still lands on the correct instant even though the JVM is set to US/Pacific, that proves the DbTime.UTC calendar is doing its job and the test will fail the day someone removes it.

@Gautam-aman

Copy link
Copy Markdown
Author

Updated the regression test to exercise the actual timestamp read path using a mocked ResultSet rather than asserting the UTC constant directly. The test now runs under a non-UTC JVM timezone and verifies the returned instant matches the expected UTC instant.

Resolve conflict in Getter.java. The dev branch refactored authUser and
authUserSSO for connection pooling (OWASP#816), moving the suspension reads to
new locations. Re-applied the UTC calendar argument to both of dev's
suspendedUntil reads:

  - authUser:    userResult.getTimestamp(8, DbTime.UTC)
  - authUserSSO: userResult.getTimestamp(1, DbTime.UTC)
ismisepaul
ismisepaul previously approved these changes Jun 4, 2026
@ismisepaul

ismisepaul commented Jun 4, 2026

Copy link
Copy Markdown
Member

@Gautam-aman thanks for that a couple of things more (there's been a big change merged since your PR #816 so I went ahead and resolved the conflicts)

  • CI red (spotless): no actual test failed, just formatting - DbTimeTest.java uses tabs + no trailing newline. If you could run mvn spotless:apply and commit (needs Java 17).
  • DbTime.UTC isn't thread-safe: Calendar is mutable and shared across concurrent logins. Use ThreadLocal
  • DbTimeTest doesn't test the fix: it stubs the method under test, so it'd pass even if DbTime weren't UTC. Make it exercise real UTC-vs-JVM conversion.

Side note: issue #841 captures some other concerns related to this - schedule gates, non-UTC ITs - not for this PR just noting here.

@Gautam-aman

Copy link
Copy Markdown
Author

Addressed the thread-safety concern by changing the shared UTC calendar to a ThreadLocal<Calendar> and updated the timestamp reads accordingly.
I also rebased onto the latest branch changes and verified the project builds successfully locally.

@ismisepaul

Copy link
Copy Markdown
Member

@Gautam-aman thanks for sticking with this. The DbTime.java ThreadLocal change is spot on. There's one issue left though, and it's why integration-tests is failing.

The Getter.java in the PR has reverted the connection-pooling work from #816 (and the rowset cap from #839). The failing check shows it directly:

Getter.java:87 - Could create get core connection:
SQLTransientConnectionException: CorePool - Connection is not available, request timed out after 5000ms.
That's the exact connection-exhaustion bug #816 fixed, coming back because the Phase 1/2/3 connection handling (and MAX_ROWSET_ROWS / CachedRowSet) got dropped from Getter.java. It builds for you locally because the old code still compiles fine — the connection leak only shows up under the integration-test load, not at compile time.

What happened is the merge took your branch's older Getter.java over dev's version rather than combining them. The fix is to take dev's Getter.java as-is and re-apply just your two timestamp reads on top, I think something like

git fetch upstream
git checkout fix/timezone-auth-bypass
git checkout upstream/dev -- src/main/java/dbProcs/Getter.java

Then in that file re-add only:

  • import utils.DbTime;
  • in authUser: suspendedUntil = userResult.getTimestamp(8, DbTime.UTC.get());
  • in authUserSSO: suspendedUntil = userResult.getTimestamp(7, DbTime.UTC.get());
git add src/main/java/dbProcs/Getter.java
git commit -m "fix: restore dev Getter.java, keep only UTC timestamp reads"
git push

After that the Getter.java diff should be ~5 lines instead of ~1500, and the connection-pool test failure will clear. DbTime.java is good as-is — no change needed there.

@Gautam-aman

Copy link
Copy Markdown
Author

Thanks for the review and apologies for the delay in addressing this. I was tied up with other commitments for a bit.
I've now rebased onto the latest branch changes, restored Getter.java from the current branch state as suggested, and kept only the UTC timestamp handling changes. I also verified the project builds successfully locally after the rebase.
Please let me know if there's anything else you'd like adjusted.

SeanDuggan and others added 3 commits June 16, 2026 10:10
Getter.java had drifted to a pre-OWASP#816 version due to a bad merge,
dropping the connection-pool refactor and CachedRowSet cap. This caused
the integration-test connection-exhaustion failure.

Restore Getter.java from upstream/dev and re-apply only the two UTC
Calendar reads for suspendedUntil, as suggested in review.
@Gautam-aman

Copy link
Copy Markdown
Author

Addressed the latest feedback and verified all checks are now passing. Please let me know if there's anything else that should be updated; otherwise I believe this is ready for another review.

Thanks again for the guidance throughout the review process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

3 participants