Skip to content

Timing Attack in GitLab Webhook Token Validation

Moderate
andrasbacsai published GHSA-x525-46rq-mr8c Jun 25, 2026

Package

coollabsio/coolify

Affected versions

<= 4.0.0-beta.460

Patched versions

>= 4.0.0-beta.461

Description

Vulnerability Description

The GitLab webhook endpoint uses a non-constant-time string comparison operator (!==) to validate the webhook secret token. This implementation is vulnerable to timing attacks, which could allow an attacker to gradually discover the secret token by measuring response time differences.

Technical Details

Vulnerable Code Location: app/Http/Controllers/Webhook/Gitlab.php:103

$webhook_secret = data_get($application, 'manual_webhook_secret_gitlab');
if ($webhook_secret !== $x_gitlab_token) {
    // Returns "Invalid signature"
}

PHP's standard string comparison operator (!==) performs a byte-by-byte comparison and returns as soon as a mismatch is found. This creates a timing side-channel where:

  • Tokens with more matching prefix characters take longer to compare
  • An attacker can statistically determine each character of the token by measuring response times

Comparison with Secure Implementation

The GitHub webhook handler in the same codebase correctly uses hash_equals():

Secure Code: app/Http/Controllers/Webhook/Github.php:83

$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
if (! hash_equals($x_hub_signature_256, $hmac) && ! isDev()) {
    // Returns "Invalid signature"
}

The Bitbucket webhook handler also uses the secure approach:

Secure Code: app/Http/Controllers/Webhook/Bitbucket.php:64

$payloadHash = hash_hmac($algo, $payload, $webhook_secret);
if (! hash_equals($hash, $payloadHash) && ! isDev()) {
    // Returns "Invalid signature"
}

Attack Scenario

Prerequisites

  1. Attacker knows the GitLab repository name associated with a Coolify application
  2. Attacker has network access to the Coolify webhook endpoint
  3. Network conditions are stable enough to measure timing differences (typically < 50ms jitter)

Attack Steps

  1. Reconnaissance: Attacker identifies an application using GitLab webhooks (e.g., through public deployment logs or error messages)

  2. Baseline Measurement: Attacker sends multiple requests with random tokens to establish baseline response times

  3. Character Enumeration: For each position in the token:

    • Send requests with all possible characters at that position
    • Measure response times for each request
    • The character producing the longest response time is likely correct
  4. Token Recovery: After recovering all characters, attacker possesses the valid webhook token

  5. Exploitation: With the valid token, attacker can:

    • Trigger unauthorized deployments
    • Inject malicious code through fake push events
    • Disrupt CI/CD pipelines

Proof of Concept

A working PoC script is available that demonstrates this attack:

# timing_attack.py (excerpt)
def measure_request(self, token: str, samples: int = 10) -> float:
    times = []
    for _ in range(samples):
        start = time.perf_counter()
        response = self.session.post(
            self.webhook_url,
            data=payload,
            headers={"X-Gitlab-Token": token}
        )
        end = time.perf_counter()
        times.append(end - start)
    return statistics.median(times)

Impact Assessment

Confidentiality

  • HIGH: Webhook secrets can be recovered, exposing authentication credentials

Integrity

  • HIGH: Attacker can trigger unauthorized deployments and inject malicious code

Availability

  • MEDIUM: Attacker could disrupt deployments or trigger excessive builds

CVSS 3.1 Score Calculation

Vector: AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N
Score: 5.3 (Medium)
  • Attack Vector: Network (AV:N)
  • Attack Complexity: High (AC:H) - requires many requests and stable network
  • Privileges Required: None (PR:N)
  • User Interaction: None (UI:N)
  • Scope: Unchanged (S:U)
  • Confidentiality: High (C:H) - token disclosure
  • Integrity: Low (I:L) - unauthorized deployments possible
  • Availability: None (A:N)

Recommended Fix

Immediate Fix

Replace the vulnerable comparison with hash_equals():

// File: app/Http/Controllers/Webhook/Gitlab.php
// Line: 103

// Before (vulnerable):
if ($webhook_secret !== $x_gitlab_token) {

// After (secure):
if (!hash_equals($webhook_secret ?? '', $x_gitlab_token ?? '')) {

Complete Patch

--- a/app/Http/Controllers/Webhook/Gitlab.php
+++ b/app/Http/Controllers/Webhook/Gitlab.php
@@ -100,7 +100,7 @@ class Gitlab extends Controller
         }
         foreach ($applications as $application) {
             $webhook_secret = data_get($application, 'manual_webhook_secret_gitlab');
-            if ($webhook_secret !== $x_gitlab_token) {
+            if (!hash_equals($webhook_secret ?? '', $x_gitlab_token ?? '')) {
                 $return_payloads->push([
                     'application' => $application->name,
                     'status' => 'failed',

Why This Fix Works

hash_equals() is specifically designed for comparing secrets:

  • Performs comparison in constant time regardless of where strings differ
  • Prevents timing side-channels
  • Available in PHP 5.6.0+ (built-in function)

References

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
Low
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N

CVE ID

CVE-2026-27882

Weaknesses

Observable Timing Discrepancy

Two separate operations in a product require different amounts of time to complete, in a way that is observable to an actor and reveals security-relevant information about the state of the product, such as whether a particular operation was successful or not. Learn more on MITRE.

Credits