Sync-in Server v230, TOTP Lockout Bypass, CVE ID: Not Provided (Severity Not Stated) -DC-Sep2026-2503

Listen to this Post

Sync-in Server v2.3.0 exposes POST /api/app/sync/register.

The endpoint accepts credentials and a TOTP code.

It is used to register a desktop sync client.

@AuthTokenSkip() bypasses the bearer-token guard.

The route is reachable without prior session.

SyncClientsManager.register() handles the request.

On failed TOTP, it calls updateAccesses(user, ip, false).

updateAccesses() defaults isAuthTwoFa to false.

If TOTP enabled site-wide and account has 2FA active, freeze branch triggers.

The freeze writes passwordAttempts back unchanged.

The counter never reaches USER_MAX_PASSWORD_ATTEMPTS.

USER_MAX_PASSWORD_ATTEMPTS is 10.

The account lockout gate never fires.

This allows repeated TOTP failures through this endpoint.

A successful TOTP guess registers a sync client.

It returns clientId and clientToken.

The account needs desktop app permission.

The registration payload must be valid.

The token can be exchanged via POST /api/app/sync/auth/cookie.

This yields an authenticated session.

While guessed TOTP code is still valid, attacker can call POST /api/auth/2fa/disable.

Attacker already knows password.

MFA can be removed.

The freeze was designed for web login flow.

Correct password at POST /api/auth/login produces partial session.

Counter should be preserved until POST /api/auth/2fa/login/verify.

That route passes isAuthTwoFa=true and increments on failure.

register() reused updateAccesses() with default false.

Same freeze also applied to logUser().

logUser() is called by POST /api/auth/login and POST /api/auth/token.

Wrong password for 2FA account preserved passwordAttempts.

Remediation adds && success to freeze condition.

That restores lockout after 10 failures.

DailyCVE Form:

Platform: Sync-in Server v2.3.0
Version: v2.3.0
Vulnerability : TOTP lockout bypass
Severity: Not stated
date: Not provided

Prediction: Unknown patch date

What Undercode Say:

Analytics:

python3 poc_totp_bruteforce.py --url http://192.168.16.132:8080 --user mfatest --password 'Str0ngP@ss99!' --concurrency 4 --batch 100
@Post(SYNC_ROUTE.REGISTER)
@AuthTokenSkip()
register(@Body() syncClientRegistrationDto: SyncClientRegistrationDto, @Req() req: FastifyRequest): Promise<SyncClientAuthRegistration> {
return this.syncClientsManager.register(syncClientRegistrationDto, req.ip)
}
this.usersManager.updateAccesses(user, ip, false).catch((e: Error) => this.logger.error({ tag: this.register.name, msg: `${e}` }))
throw new HttpException(authCode.message, HttpStatus.UNAUTHORIZED)
async updateAccesses(user: UserModel, ip: string, success: boolean, isAuthTwoFa = false) {
let passwordAttempts: number
if (!isAuthTwoFa && configuration.auth.mfa.totp.enabled && user.twoFaEnabled) {
passwordAttempts = user.passwordAttempts
} else {
passwordAttempts = success ? 0 : Math.min(user.passwordAttempts + 1, USER_MAX_PASSWORD_ATTEMPTS)
}
await this.usersQueries.updateUserOrGuest(user.id, {
...
passwordAttempts: passwordAttempts,
isActive: user.isActive && passwordAttempts < USER_MAX_PASSWORD_ATTEMPTS
})
}
// Before
if (!isAuthTwoFa && configuration.auth.mfa.totp.enabled && user.twoFaEnabled) {
// After
if (!isAuthTwoFa && configuration.auth.mfa.totp.enabled && user.twoFaEnabled && success) {
Total attempts : 195,907
Time elapsed : 17769.5s (296.2min)
Average RPS : 11.0
VALID TOTP CODE FOUND : 026961
clientId : 13951b88-03d4-4854-8a29-cd8921d73d82
clientToken : c92ef5ca-7432-44d0-8a94-56398bfe4117
passwordAttempts : 0
2FA DISABLED. Account 'mfatest' now accessible with password alone.

Exploit: (Educational Purposes!)

Create test account with TOTP MFA and desktop sync permission.

Run poc_totp_bruteforce.py.

Brute-force TOTP via POST /api/app/sync/register.

On valid code, obtain clientId and clientToken.

Exchange via POST /api/app/sync/auth/cookie.

Call POST /api/auth/2fa/disable while TOTP valid.

Protection:

Add && success to freeze condition.

Apply IP and account rate limiter.

Rate limit POST /api/app/sync/register.

Rate limit pre-auth credential endpoints.

Monitor repeated TOTP failures.

Impact:

An attacker with valid credentials for TOTP-enabled account with desktop sync permission can brute-force second factor through POST /api/app/sync/register without lockout.
With drift 1, 3 of 1,000,000 six-digit codes valid per 30-second window (p = 3/1,000,000).

Expected 333,333 attempts to find valid code.

At 3 r/s: 50% = 231,049 attempts, 21.4 h.
At 3 r/s: 90% = 767,528 attempts, 71.1 h.
At 3 r/s: 95% = 998,577 attempts, 92.5 h.
At 3 r/s: 99% = 1,535,056 attempts, 142.1 h.
At 3 r/s: Expected mean = 333,333 attempts, 30.9 h.
Deployments with server.workers > 1 may allow higher throughput.
Throughput depends on password verification cost, worker count, database latency, deployment limits.

🎯Let’s Practice Exploiting & Learn Patching For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top