Back to Blog
critical SEVERITY4 min read

Hybridauth Telegram OAuth Timing Attack in `authenticateCheckError()`

The Telegram OAuth provider in Hybridauth relied on `strcmp()` to validate HMAC-SHA256 signatures, exposing a timing side-channel that could allow attackers to forge authentication tokens character by character. The fix replaces this with PHP's timing-safe `hash_equals()` function, eliminating the information leak.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published October 2, 2026•Reviewed October 2, 2026

Answer Summary

The `authenticateCheckError()` method in Hybridauth's Telegram OAuth provider used `strcmp($hash, $check_hash)` for HMAC-SHA256 verification. An attacker could measure timing differences in the byte-by-byte comparison to progressively guess valid hash values and forge Telegram authentication callbacks. The fix replaces `strcmp()` with `hash_equals($hash, $check_hash)` in the signature validation routine. CWE status is unknown.

Vulnerability at a Glance

cweN/A
fixReplace strcmp() with hash_equals() for constant-time HMAC verification
riskAuthentication bypass via forged Telegram OAuth tokens
languagePHP
root causestrcmp() leaks timing information through early-return comparison
vulnerabilityTiming side-channel in cryptographic comparison

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see PR for commit
Ecosystem not applicable (first-party code)
CVE / GHSA not assigned
CWE unknown

Introduction

A critical authentication flaw in Hybridauth's Telegram OAuth provider allowed attackers to forge authentication tokens through precise timing analysis. The authenticateCheckError() method—which validates Telegram's callback data using HMAC-SHA256—relied on PHP's strcmp() function to compare computed and received hash values. This function returns immediately on the first mismatched byte, creating measurable timing differences that leak information about the correct hash prefix.

The vulnerability is particularly insidious because the cryptographic implementation appears correct at first glance: the code properly computes an HMAC using hash_hmac(), uses a SHA-256 derived key, and concatenates parameters in Telegram's specified format. Yet a single comparison operation undermined the entire authentication guarantee.

The Vulnerability Explained

The vulnerable code in authenticateCheckError() constructed a data verification string from Telegram's callback parameters, computed an expected HMAC, then validated it:

$secret_key = hash('sha256', $this->botSecret, true);
$hash = hash_hmac('sha256', $data_check_string, $secret_key);

if (strcmp($hash, $check_hash) !== 0) {
    throw new InvalidAuthorizationCodeException(
        sprintf('Provider returned an error: %s', 'Data is NOT from Telegram')
    );
}

The strcmp() function compares strings lexicographically, returning as soon as it finds a difference. For two 64-character hex strings, a comparison that fails at position 5 executes faster than one that fails at position 60. An attacker sending forged callbacks with systematically varied hash values can measure response times to determine how many leading characters match the valid hash.

Attack scenario: An attacker intercepts or constructs a Telegram callback with manipulated user data (claiming to be an admin user, for instance). They send guesses for the $check_hash parameter, starting with 000...0 through fff...f. When a guess with prefix a7f takes measurably longer than a7e, they've confirmed the first three characters. Repeating this process 64 times yields a complete valid hash, allowing complete authentication bypass.

The real-world impact is severe: any service using this provider for Telegram login could have attacker-controlled accounts elevated to arbitrary identities, including administrative accounts if the OAuth flow supports role assignment.

The Fix

The fix replaces strcmp() with PHP's hash_equals() function, which performs constant-time comparison regardless of where strings differ:

$secret_key = hash('sha256', $this->botSecret, true);
$hash = hash_hmac('sha256', $data_check_string, $secret_key);

if (!hash_equals($hash, $check_hash)) {
    throw new InvalidAuthorizationCodeException(
        sprintf('Provider returned an error: %s', 'Data is NOT from Telegram')
    );
}

hash_equals() was introduced in PHP 5.6.0 specifically to address this class of vulnerability. It compares all bytes of both strings before returning, and uses operations whose execution time does not depend on the data content. The logic change is minimal—strcmp() !== 0 becomes !hash_equals()—but the security property transformation is fundamental.

The surrounding code including $secret_key derivation, $data_check_string construction from Telegram's auth_date, id, and other parameters, and the exception throwing logic remain unchanged. This surgical fix preserves all existing functionality while eliminating the side-channel.

Key Takeaways

  • strcmp() and === on sensitive values leak timing information—always use hash_equals() for HMAC, password hash, or cryptographic signature comparison in PHP, even when the values are hex-encoded strings.

  • Correct cryptography adjacent to vulnerable code is still vulnerable—the HMAC computation itself was flawless, but the comparison operation invalidated the security guarantee. Review authentication logic end-to-end, not just the cryptographic primitives.

  • OAuth provider implementations are high-value targets—as centralized authentication gateways, flaws here compromise every downstream service. Third-party authentication libraries warrant security audits comparable to primary application code.

  • Timing attacks are practical over network distances—modern statistical techniques and repeated measurements can extract timing differences in the nanosecond range even across internet paths. Never assume network jitter protects against these attacks.

How Orbis AppSec Detected This

Orbis AppSec identified this vulnerability through static analysis of the authentication flow in the Telegram OAuth provider implementation.

  • Source: The $check_hash parameter from Telegram's OAuth callback data, received via HTTP request
  • Sink: The strcmp() function comparing computed and received HMAC values in authenticateCheckError()
  • Missing control: Constant-time comparison primitive; strcmp() was used where hash_equals() is required for cryptographic verification
  • CWE: unknown
  • Fix: Replace strcmp($hash, $check_hash) with hash_equals($hash, $check_hash) to eliminate timing side-channel

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability demonstrates how a single function choice—strcmp() versus hash_equals()—can undermine otherwise sound cryptography. The Telegram OAuth provider's HMAC-SHA256 implementation was algorithmically correct, yet the timing side-channel in signature comparison rendered it exploitable. For developers integrating OAuth providers or implementing any cryptographic verification, this case underscores that security requires correct primitives at every step: generation, computation, encoding, and comparison. The fix is now available; services using this provider should apply the update immediately.

Prevention and further reading

Frequently Asked Questions

Does the Telegram OAuth provider's use of `hash_hmac('sha256', ...)` protect against the timing attack?

No. While `hash_hmac()` correctly computes the HMAC, the subsequent `strcmp()` comparison leaks timing information. The cryptographic computation was sound; the vulnerability was in the comparison operation at line 119.

Why couldn't an attacker simply brute-force the 64-character hex hash directly?

The timing side-channel allows a byte-by-byte attack with 256 possibilities per position rather than 16^64 exhaustive search. Each correct prefix adds measurable latency, reducing the attack from computationally impossible to practical with network timing analysis.

Is the `$data_check_string` construction in `authenticateCheckError()` affected by this change?

No. The data concatenation logic using `$this->botSecret` and the checked parameters remains identical. Only the final comparison operation changes from `strcmp()` to `hash_equals()`.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #552

Related Articles

critical

Node.js Auth Query SQL Injection via Group Code Interpolation

A sign-in authorization check built its SQL query by mapping an `authorizedGroups` array into quoted string literals and joining them directly into a template literal, creating a classic SQL injection point in a critical authentication path. The fix replaces every interpolated value — including the previously "typed" parameter — with `?` placeholders bound through a value-builder helper, closing off the injection vector entirely.

high

PHP session_start() Missing Secure Cookie Flags in lasturl.php

The `session_start()` call in the last URL tracking component failed to set secure session cookie parameters, allowing session hijacking via man-in-the-middle attacks on HTTP connections or XSS exploitation. The fix configures `session_set_cookie_params()` with `httponly`, `secure`, and `samesite` attributes before starting the session.

critical

Tung Tung Tracker Admin API: Missing Authentication in `/api/sync`

The Tung Tung Tracker administrative API endpoints (`/api/sync`, `/api/backfill`, `/api/show`, `/api/overlay/hide`) relied solely on localhost origin and a static `X-Tracker` header for protection, allowing any local process to execute privileged operations. The fix introduces proper session-based authentication and a random IPC secret for desktop-to-server communication.

critical

POST /api/generate Lacks Authentication, Allowing Unauthenticated

A resume generation endpoint in a Node.js backend accepted requests from any caller with network access, allowing attackers to consume OpenAI API quota without restriction. The vulnerability stemmed from missing authentication middleware on a cost-bearing endpoint. The fix adds mandatory API key validation via HTTP headers before processing any generation requests.

critical

chrome.runtime.onMessage: Missing Sender Validation in Extension

A critical authentication flaw in a Chrome extension's message handling allowed arbitrary extensions and malicious web pages to trigger sensitive operations including script injection and tab manipulation. The vulnerability existed because chrome.runtime.onMessage listeners processed requests without verifying sender identity or origin, trusting any message source by default.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.