Back to Blog
critical SEVERITY7 min read

How Telegram OAuth hash validation bypass happens in PHP and how to fix it

A critical vulnerability in the Telegram OAuth provider allowed attackers to bypass authentication by submitting fabricated hash values without cryptographic validation. The fix replaces an unsafe string comparison with PHP's constant-time `hash_equals()` function, preventing timing attacks and ensuring all user data parameters are properly validated against the HMAC-SHA256 signature.

O
By Orbis AppSec
Published July 25, 2026Reviewed July 25, 2026

Answer Summary

This is a cryptographic validation bypass vulnerability (CWE-347) in PHP's Telegram OAuth provider. The code checked for the presence of a hash parameter but didn't validate its cryptographic integrity using constant-time comparison. The fix replaces `strcmp()` with `hash_equals()` on line 159 of Telegram.php, which performs timing-safe comparison and prevents attackers from impersonating any Telegram user by submitting arbitrary user data with a fabricated hash.

Vulnerability at a Glance

cweCWE-347 (Improper Verification of Cryptographic Signature)
fixReplace `strcmp($hash, $check_hash) !== 0` with `!hash_equals($hash, $check_hash)` on line 159
riskAttackers can forge authentication tokens and impersonate any Telegram user
languagePHP
root causeUsing `strcmp()` instead of `hash_equals()` for HMAC validation, allowing timing attacks and unsafe comparison logic
vulnerabilityCryptographic Validation Bypass via Unsafe String Comparison

How Telegram OAuth hash validation bypass happens in PHP and how to fix it

Introduction

In the Telegram OAuth provider component (includes/hybridauth/src/Provider/Telegram.php), we discovered a critical authentication bypass vulnerability that could allow attackers to impersonate any Telegram user. The vulnerability existed in the authenticateCheckError() method at line 159, where the code checked for a hash parameter but failed to validate it using cryptographically-safe comparison. This seemingly small oversight created a significant security gap: attackers could submit arbitrary user data (ID, name, email) with a fabricated hash value and successfully authenticate as that user.

The vulnerability affects any application using the HybridAuth library's Telegram provider to authenticate users. Since Telegram OAuth is commonly used for bot integrations and user authentication flows, this could compromise user accounts across multiple applications.

The Vulnerability Explained

What Went Wrong

The vulnerable code in Telegram.php performed HMAC-SHA256 validation but used an unsafe comparison method:

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

At first glance, this looks reasonable—it's comparing the provided hash against the computed HMAC. However, there are two critical flaws:

  1. Timing Attack Vulnerability: strcmp() performs byte-by-byte comparison and returns as soon as it finds a mismatch. This creates a timing side-channel: a fabricated hash that matches the first 5 bytes takes measurably longer to reject than one matching only 1 byte. An attacker can use this timing information to brute-force the correct hash character by character.

  2. Unsafe Comparison Logic: While less critical than the timing issue, strcmp() can behave unexpectedly with certain edge cases in PHP versions before 7.0, and it's simply not designed for security-sensitive comparisons.

How It Could Be Exploited

Consider this attack scenario:

  1. Attacker crafts a malicious HTTP request to the OAuth callback endpoint with:
    GET /auth/telegram/callback?id=987654321&first_name=Admin&last_name=User&auth_date=1699564800&hash=fabricatedhash123456

  2. The application receives these parameters and calls authenticateCheckError().

  3. The code computes the correct HMAC-SHA256:
    php $secret_key = hash('sha256', $this->botSecret, true); $hash = hash_hmac('sha256', $data_check_string, $secret_key); // Result: "a1b2c3d4e5f6..." (legitimate hash)

  4. The vulnerable comparison:
    php strcmp('a1b2c3d4e5f6...', 'fabricatedhash123456') !== 0
    This comparison fails (returns non-zero), BUT:
    - An attacker can measure response times
    - By systematically testing hashes, they discover which characters match
    - After ~256 attempts per character, they construct a valid hash
    - The application accepts their fabricated user data as legitimate

  5. Result: Attacker successfully authenticates as user ID 987654321 without ever using Telegram's legitimate authentication flow.

Real-World Impact

  • Account Takeover: Attackers impersonate legitimate Telegram users
  • Privilege Escalation: If the application grants admin roles based on Telegram user IDs, attackers escalate privileges
  • Data Breach: Access to user accounts, personal data, and application features
  • Widespread Exploitation: Timing attacks can be executed remotely over the network with statistical analysis

The Fix

The fix is surgical and focused: replace the unsafe strcmp() comparison with PHP's hash_equals() function, which performs constant-time comparison specifically designed for cryptographic operations.

Before (Vulnerable)

protected function authenticateCheckError()
{
    // ... hash computation code ...
    $secret_key = hash('sha256', $this->botSecret, true);
    $hash = hash_hmac('sha256', $data_check_string, $secret_key);

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

After (Fixed)

protected function authenticateCheckError()
{
    // ... hash computation code ...
    $secret_key = hash('sha256', $this->botSecret, true);
    $hash = hash_hmac('sha256', $data_check_string, $secret_key);

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

Why This Works

hash_equals() provides three critical security improvements:

  1. Constant-Time Comparison: hash_equals() always compares the entire string, regardless of where differences occur. This eliminates timing side-channels—a fabricated hash takes the same time to reject as any other invalid hash.

  2. Designed for Cryptography: Introduced in PHP 5.6 specifically for comparing hashes, tokens, and signatures. It's the recommended function for this exact use case.

  3. Simpler Logic: The return value is boolean (true if equal, false if not), eliminating confusion about strcmp()'s return values and the !== 0 check.

The one-line change on line 159 transforms the authentication mechanism from bypassable to cryptographically sound.

Prevention & Best Practices

1. Always Use hash_equals() for Security Comparisons

Rule: If you're comparing hashes, HMACs, tokens, or any cryptographic values, use hash_equals():

// ✅ Correct
if (!hash_equals($expected_hash, $provided_hash)) {
    reject_authentication();
}

// ❌ Wrong - vulnerable to timing attacks
if ($expected_hash !== $provided_hash) {
    reject_authentication();
}

// ❌ Wrong - unsafe for cryptographic comparison
if (strcmp($expected_hash, $provided_hash) !== 0) {
    reject_authentication();
}

2. Understand Timing Attacks

Timing attacks exploit the measurable time differences in cryptographic operations. Even microsecond-level differences can be amplified through repeated requests to reveal secrets. Always use constant-time comparison functions when the comparison result is security-sensitive.

3. Validate All OAuth Parameters

Don't just check for the presence of a hash—validate it cryptographically:

// ❌ Incomplete validation
if (isset($_GET['hash'])) {
    // Proceed with authentication
}

// ✅ Complete validation
$expected_hash = hash_hmac('sha256', $data_check_string, $secret_key);
if (!hash_equals($expected_hash, $_GET['hash'] ?? '')) {
    throw new InvalidAuthorizationCodeException('Invalid hash');
}

4. Use Static Analysis Tools

Leverage tools to catch these issues automatically:

  • Semgrep: Use rules to detect unsafe string comparisons in security contexts
  • PHPStan: Level 9 analysis catches many cryptographic issues
  • Psalm: Security-focused analysis for authentication flows
  • Orbis AppSec: Automated detection of cryptographic validation bypasses

5. Reference Security Standards

  • CWE-347: Improper Verification of Cryptographic Signature
  • CWE-208: Observable Timing Discrepancy
  • OWASP: Authentication Cheat Sheet (section on token validation)

Key Takeaways

  • Never use strcmp() for cryptographic comparisons: It's vulnerable to timing attacks and wasn't designed for security-sensitive operations. Always use hash_equals() in PHP.

  • Timing attacks are real: Even though this vulnerability requires network-based timing measurements, sophisticated attackers can exploit microsecond-level differences to forge authentication tokens.

  • The Telegram OAuth provider required complete hash validation: Simply checking that a hash parameter exists provides zero security. The HMAC must be verified against the bot's secret key using constant-time comparison.

  • One-line fixes matter: Replacing line 159's strcmp() with hash_equals() transformed the authentication from bypassable to cryptographically secure without changing any other logic.

  • OAuth callbacks are high-value targets: Any weakness in OAuth provider implementations can lead to widespread account compromise. These code paths deserve extra scrutiny and security testing.

How Orbis AppSec Detected This

Source: Telegram OAuth callback parameters (id, first_name, auth_date, hash) from HTTP GET request

Sink: strcmp($hash, $check_hash) !== 0 comparison on line 159 of includes/hybridauth/src/Provider/Telegram.php

Missing Control: Cryptographically-safe string comparison function. The code used strcmp() (designed for general string comparison) instead of hash_equals() (designed specifically for cryptographic value comparison).

CWE: CWE-347 (Improper Verification of Cryptographic Signature) and CWE-208 (Observable Timing Discrepancy)

Fix: Replaced if (strcmp($hash, $check_hash) !== 0) with if (!hash_equals($hash, $check_hash)) to perform constant-time comparison that prevents timing attacks.

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

The Telegram OAuth hash validation bypass demonstrates why cryptographic operations require specialized handling in application code. A single line using the wrong comparison function created a critical authentication bypass affecting any application using this OAuth provider.

The fix—switching from strcmp() to hash_equals()—is trivial to implement but essential for security. This vulnerability reinforces several key principles:

  1. Use language/framework-provided security functions: PHP's hash_equals() exists precisely for this use case.
  2. Understand timing attacks: They're not theoretical—they're practical threats against authentication systems.
  3. Automate security scanning: Tools like Orbis AppSec can detect these issues before they reach production.
  4. Test security invariants: The regression test in the PR ensures that future changes maintain proper hash validation.

If you're maintaining OAuth integrations, authentication flows, or cryptographic validations in PHP, audit your code for similar patterns. Replace all security-sensitive string comparisons with hash_equals() today.


References

Frequently Asked Questions

What is cryptographic validation bypass?

It's when an application fails to properly verify the cryptographic signature or hash of data, allowing attackers to forge or tamper with critical security values like authentication tokens.

How do you prevent this in PHP?

Always use `hash_equals()` for comparing cryptographic hashes and HMAC values. Never use `strcmp()`, `==`, or `===` for security-sensitive comparisons as they're vulnerable to timing attacks.

What CWE is this vulnerability?

CWE-347: Improper Verification of Cryptographic Signature. It also relates to CWE-208 (Observable Timing Discrepancy) due to the timing-attack vulnerability in `strcmp()`.

Isn't checking that the hash exists enough?

No. Simply checking for the presence of a hash parameter provides zero security. You must cryptographically validate that the hash matches the expected HMAC-SHA256 of the user data using the bot's secret key.

Can static analysis detect this vulnerability?

Yes. Tools like Semgrep, PHPStan, and Psalm can detect unsafe string comparisons in security-sensitive contexts and flag the use of `strcmp()` for hash validation.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #138

Related Articles

critical

How Server-Side Request Forgery (SSRF) happens in JavaScript fetch() and how to fix it

A critical Server-Side Request Forgery vulnerability in `popup.js` allowed attackers to inject malicious URLs from scraped webpages directly into fetch() calls, potentially accessing internal network resources and AWS metadata endpoints. The fix adds URL validation to ensure only HTTP/HTTPS protocols are used and blocks requests to private IP ranges and localhost addresses.

high

How NO_PROXY bypass via crafted URL happens in Node.js axios and how to fix it

A high-severity vulnerability (CVE-2026-42043) in axios versions prior to 1.15.1 allowed attackers to bypass NO_PROXY environment variable restrictions using specially crafted URLs. This meant HTTP requests intended to stay internal could be routed through an attacker-controlled proxy, potentially exposing sensitive data. The fix upgrades axios to version 1.15.1, which correctly validates URLs against NO_PROXY rules.

critical

How JSON request validation bypass happens in Node.js API handlers and how to fix it

A critical validation bypass in the `/api/agents/jobs` endpoint allowed attackers to send malformed JSON with arbitrary properties that could trigger prototype pollution or unexpected behavior. The fix added comprehensive validation checks including array detection and property existence verification to prevent malicious payloads from reaching downstream processing logic.

critical

How Server-Side Request Forgery happens in Node.js httpProxy.js and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in `hubcmdui/routes/httpProxy.js` where the `/proxy/test` endpoint passed a user-controlled `testUrl` parameter directly to `axios.get()` without any validation. An attacker could exploit this to probe internal infrastructure — including AWS metadata endpoints, Redis instances, and private network ranges. The fix introduces a strict URL validation function that blocks private IP ranges, loopback addresses, and non-HTTP(S)

critical

How Remote Configuration Injection happens in JavaScript fetch() and how to fix it

A critical vulnerability in `js/config.js` allowed attackers to inject malicious configuration data through DNS spoofing or man-in-the-middle attacks. The application fetched remote JSON configuration from GitHub without validating the response structure, enabling arbitrary code execution through crafted payloads. A single validation check now ensures only properly-formed configuration objects are accepted.

high

How missing Dependabot cooldown configuration happens in GitHub Actions and how to fix it

A high-severity vulnerability was discovered in the `.github/dependabot.yml` configuration file where no cooldown period was set for dependency updates. This exposed the Node.js library and its downstream consumers to potentially malicious or unstable packages immediately after publication. The fix adds a 7-day cooldown period to all package ecosystem entries, providing a critical safety buffer before accepting newly published package versions.