Common Weakness Enumeration

CWE-208

Allowed

Observable Timing Discrepancy

Abstraction: Base · Status: Incomplete

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.

306 vulnerabilities reference this CWE, most recent first.

GHSA-JXQV-JCVH-7GR4

Vulnerability from github – Published: 2022-07-30 00:00 – Updated: 2024-05-20 21:28
VLAI
Summary
Atlantis Events vulnerable to Timing Attack
Details

The package github.com/runatlantis/atlantis/server/controllers/events before 0.19.7 is vulnerable to Timing Attack in the webhook event validator code, which does not use a constant-time comparison function to validate the webhook secret. It can allow an attacker to recover this secret as an attacker and then forge webhook events.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/runatlantis/atlantis"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.19.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2022-24912"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-203",
      "CWE-208"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2022-08-06T05:21:43Z",
    "nvd_published_at": "2022-07-29T10:15:00Z",
    "severity": "HIGH"
  },
  "details": "The package github.com/runatlantis/atlantis/server/controllers/events before 0.19.7 is vulnerable to Timing Attack in the webhook event validator code, which does not use a constant-time comparison function to validate the webhook secret. It can allow an attacker to recover this secret as an attacker and then forge webhook events.",
  "id": "GHSA-jxqv-jcvh-7gr4",
  "modified": "2024-05-20T21:28:30Z",
  "published": "2022-07-30T00:00:41Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24912"
    },
    {
      "type": "WEB",
      "url": "https://github.com/runatlantis/atlantis/issues/2391"
    },
    {
      "type": "WEB",
      "url": "https://github.com/runatlantis/atlantis/pull/2392"
    },
    {
      "type": "WEB",
      "url": "https://github.com/runatlantis/atlantis/commit/48870911974adddaa4c99c8089e79b7d787fa820"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/runatlantis/atlantis"
    },
    {
      "type": "WEB",
      "url": "https://pkg.go.dev/vuln/GO-2022-0534"
    },
    {
      "type": "WEB",
      "url": "https://security.snyk.io/vuln/SNYK-GOLANG-GITHUBCOMRUNATLANTISATLANTISSERVERCONTROLLERSEVENTS-2950851"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Atlantis Events vulnerable to Timing Attack"
}

GHSA-M38J-PMG3-V5X5

Vulnerability from github – Published: 2020-06-23 19:58 – Updated: 2024-11-18 16:26
VLAI
Summary
Timing attack on django-basic-auth-ip-whitelist
Details

Impact

Potential timing attack exists on websites where the basic authentication is used or configured, i.e. BASIC_AUTH_LOGIN and BASIC_AUTH_PASSWORD is set. Currently the string comparison between configured credentials and the ones provided by users is performed through a character-by-character string comparison. This enables a possibility that attacker may time the time it takes the server to validate different usernames and password, and use this knowledge to work out the valid credentials. This attack is understood not to be realistic over the Internet. However, it may be achieved from within local networks where the website is hosted, e.g. from inside a data centre where a website's server is located.

Sites protected by IP address whitelisting only are unaffected by this vulnerability.

Patches

This vulnerability has been fixed on version 0.3.4 of django-basic-auth-ip-whitelist. Update to version 0.3.4 as soon as possible and change basic authentication username and password configured on a Django project using this package.

Workarounds

Stop using basic authentication and use the IP whitelisting component only. It can be achieved by not setting BASIC_AUTH_LOGIN and BASIC_AUTH_PASSWORD in Django project settings.

References

For more information

If you have any questions or comments about this advisory: * Open an issue at https://github.com/tm-kn/django-basic-auth-ip-whitelist/issues * Email us at the email specified on the security policy.

Acknowledgements

Thanks to Thibaud Colas for reporting this.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "django-basic-auth-ip-whitelist"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.3.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2020-4071"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-208"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2020-06-23T19:56:14Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "### Impact\n\nPotential timing attack exists on websites where the basic authentication is used or configured, i.e. `BASIC_AUTH_LOGIN` and `BASIC_AUTH_PASSWORD` is set. Currently the string comparison between configured credentials and the ones provided by users is performed through a character-by-character string comparison. This enables a possibility that attacker may time the time it takes the server to validate different usernames and password, and use this knowledge to work out the valid credentials. This attack is understood not to be realistic over the Internet. However, it may be achieved from within local networks where the website is hosted, e.g. from inside a data centre where a website\u0027s server is located.\n\nSites protected by IP address whitelisting only are unaffected by this vulnerability.\n\n### Patches\nThis vulnerability has been fixed on version 0.3.4 of django-basic-auth-ip-whitelist. Update to version 0.3.4 as soon as possible and change basic authentication username and password configured on a Django project using this package.\n\n### Workarounds\nStop using basic authentication and use the IP whitelisting component only. It can be achieved by not setting `BASIC_AUTH_LOGIN` and `BASIC_AUTH_PASSWORD` in Django project settings.\n\n### References\n\n- [Django mailing list discussion](https://groups.google.com/forum/#!msg/django-developers/iAaq0pvHXuA/fpUuwjK3i2wJ)\n\n### For more information\nIf you have any questions or comments about this advisory:\n* Open an issue at https://github.com/tm-kn/django-basic-auth-ip-whitelist/issues\n* Email us at the email specified on the [security policy](https://github.com/tm-kn/django-basic-auth-ip-whitelist/security/policy).\n\n### Acknowledgements\n\nThanks to Thibaud Colas for reporting this.",
  "id": "GHSA-m38j-pmg3-v5x5",
  "modified": "2024-11-18T16:26:09Z",
  "published": "2020-06-23T19:58:27Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/tm-kn/django-basic-auth-ip-whitelist/security/advisories/GHSA-m38j-pmg3-v5x5"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-4071"
    },
    {
      "type": "WEB",
      "url": "https://github.com/tm-kn/django-basic-auth-ip-whitelist/commit/effe05ed1ed9e1ccc675a65b69d36217e5c5dfc6"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/django-basic-auth-ip-whitelist/PYSEC-2020-37.yaml"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/tm-kn/django-basic-auth-ip-whitelist"
    },
    {
      "type": "WEB",
      "url": "https://groups.google.com/forum/#!msg/django-developers/iAaq0pvHXuA/fpUuwjK3i2wJ"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:P/AC:H/PR:N/UI:N/S:C/C:L/I:N/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Timing attack on django-basic-auth-ip-whitelist"
}

GHSA-M4F8-P58G-J8MJ

Vulnerability from github – Published: 2023-01-18 00:30 – Updated: 2023-01-25 02:56
VLAI
Summary
Observable timing discrepancy in JOpenId
Details

A vulnerability, which was classified as problematic, was found in michaelliao jopenid. Affected is the function getAuthentication of the file JOpenId/src/org/expressme/openid/OpenIdManager.java. The manipulation leads to observable timing discrepancy. Upgrading to version 1.08 is able to address this issue. The name of the patch is c9baaa976b684637f0d5a50268e91846a7a719ab. It is recommended to upgrade the affected component. The identifier of this vulnerability is VDB-218460.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.expressme:JOpenId"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.08"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2010-10006"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-203",
      "CWE-208"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2023-01-25T02:56:52Z",
    "nvd_published_at": "2023-01-18T00:15:00Z",
    "severity": "HIGH"
  },
  "details": "A vulnerability, which was classified as problematic, was found in michaelliao jopenid. Affected is the function getAuthentication of the file JOpenId/src/org/expressme/openid/OpenIdManager.java. The manipulation leads to observable timing discrepancy. Upgrading to version 1.08 is able to address this issue. The name of the patch is c9baaa976b684637f0d5a50268e91846a7a719ab. It is recommended to upgrade the affected component. The identifier of this vulnerability is VDB-218460.",
  "id": "GHSA-m4f8-p58g-j8mj",
  "modified": "2023-01-25T02:56:52Z",
  "published": "2023-01-18T00:30:18Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2010-10006"
    },
    {
      "type": "WEB",
      "url": "https://github.com/michaelliao/jopenid/commit/c9baaa976b684637f0d5a50268e91846a7a719ab"
    },
    {
      "type": "WEB",
      "url": "https://github.com/michaelliao/jopenid/releases/tag/JOpenId-1.08"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?ctiid.218460"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?id.218460"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Observable timing discrepancy in JOpenId"
}

GHSA-M538-6WP4-7H22

Vulnerability from github – Published: 2026-06-12 15:30 – Updated: 2026-06-12 18:31
VLAI
Details

Crypt::PBKDF2 versions before 0.261630 for Perl are vulnerable to timing attacks.

These versions use Perl's built-in eq comparison. Discrepancies in timing could be used to guess the underlying derived-key.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2017-20240"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-208"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-06-12T14:16:28Z",
    "severity": "MODERATE"
  },
  "details": "Crypt::PBKDF2 versions before 0.261630 for Perl are vulnerable to timing attacks.\n\nThese versions use Perl\u0027s built-in eq comparison. Discrepancies in timing could be used to guess the underlying derived-key.",
  "id": "GHSA-m538-6wp4-7h22",
  "modified": "2026-06-12T18:31:53Z",
  "published": "2026-06-12T15:30:34Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-20240"
    },
    {
      "type": "WEB",
      "url": "https://github.com/arodland/Crypt-PBKDF2/pull/6"
    },
    {
      "type": "WEB",
      "url": "https://metacpan.org/release/ARODLAND/Crypt-PBKDF2-0.161520/source/lib/Crypt/PBKDF2.pm#L123-148"
    },
    {
      "type": "WEB",
      "url": "https://metacpan.org/release/ARODLAND/Crypt-PBKDF2-0.261630/changes"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2026/06/12/3"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-M7QP-CJ9P-GJ85

Vulnerability from github – Published: 2022-12-28 18:30 – Updated: 2024-03-01 14:23
VLAI
Summary
OpenShift OSIN vulnerable to Observable Timing Discrepancy
Details

A vulnerability was found in OpenShift OSIN. It has been classified as problematic. This affects the function ClientSecretMatches/CheckClientSecret. The manipulation of the argument secret leads to observable timing discrepancy. The name of the patch is 8612686d6dda34ae9ef6b5a974e4b7accb4fea29. It is recommended to apply a patch to fix this issue. The associated identifier of this vulnerability is VDB-216987.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/openshift/osin"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.0.2-0.20210113124101-8612686d6dda"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2021-4294"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-203",
      "CWE-208"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2023-01-09T21:50:04Z",
    "nvd_published_at": "2022-12-28T17:15:00Z",
    "severity": "MODERATE"
  },
  "details": "A vulnerability was found in OpenShift OSIN. It has been classified as problematic. This affects the function `ClientSecretMatches/CheckClientSecret`. The manipulation of the argument secret leads to observable timing discrepancy. The name of the patch is 8612686d6dda34ae9ef6b5a974e4b7accb4fea29. It is recommended to apply a patch to fix this issue. The associated identifier of this vulnerability is VDB-216987.",
  "id": "GHSA-m7qp-cj9p-gj85",
  "modified": "2024-03-01T14:23:07Z",
  "published": "2022-12-28T18:30:20Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-4294"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openshift/osin/pull/200"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openshift/osin/commit/8612686d6dda34ae9ef6b5a974e4b7accb4fea29"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/openshift/osin"
    },
    {
      "type": "WEB",
      "url": "https://pkg.go.dev/vuln/GO-2022-1201"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?ctiid.216987"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?id.216987"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "OpenShift OSIN vulnerable to Observable Timing Discrepancy"
}

GHSA-MJ7R-X3H3-7RMR

Vulnerability from github – Published: 2026-04-16 20:42 – Updated: 2026-04-16 20:42
VLAI
Summary
ApostropheCMS: User Enumeration via Timing Side Channel in Password Reset Endpoint
Details

Summary

The password reset endpoint (/api/v1/@apostrophecms/login/reset-request) exhibits a measurable timing side channel that allows unauthenticated attackers to enumerate valid usernames and email addresses. When a user is not found, the handler returns after a fixed 2-second artificial delay, but when a valid user is found, it performs database writes and SMTP operations with no equivalent delay normalization, producing a distinguishable timing profile.

Details

The resetRequest handler in modules/@apostrophecms/login/index.js attempts to obscure the user-not-found path with an artificial delay, but fails to normalize the timing of the user-found path:

User not found — fixed 2000ms delay (index.js:309-314):

if (!user) {
  await wait();  // wait = (t = 2000) => Promise.delay(t)
  self.apos.util.error(
    `Reset password request error - the user ${email} doesn\`t exist.`
  );
  return;
}

User found — variable-duration DB + SMTP operations, no artificial delay (index.js:323-355):

const reset = self.apos.util.generateId();
user.passwordReset = reset;
user.passwordResetAt = new Date();
await self.apos.user.update(req, user, { permissions: false });
// ... URL construction ...
await self.email(req, 'passwordResetEmail', {
  user,
  url: parsed.toString(),
  site
}, {
  to: user.email,
  subject: req.t('apostrophe:passwordResetRequest', { site })
});

The user-found path includes a MongoDB update() call and an SMTP email() send, which together produce response times that differ measurably from the fixed 2000ms delay. Depending on SMTP server latency, responses for valid users will either be noticeably faster (local/fast SMTP) or slower (remote SMTP) than the constant 2-second delay for invalid users.

Additionally, the getPasswordResetUser method (index.js:664-666) accepts both username and email via an $or query, enabling enumeration of both identifiers:

const criteriaOr = [
  { username: email },
  { email }
];

There is no rate limiting on the reset endpoint. The checkLoginAttempts throttle (index.js:978) is only applied to the login flow, allowing unlimited rapid probing of the reset endpoint.

PoC

Prerequisites: An Apostrophe instance with passwordReset: true enabled in @apostrophecms/login configuration.

Step 1 — Baseline invalid user timing:

for i in $(seq 1 10); do
  curl -s -o /dev/null -w "%{time_total}\n" \
    -X POST http://localhost:3000/api/v1/@apostrophecms/login/reset-request \
    -H "Content-Type: application/json" \
    -d '{"email": "nonexistent-user-'$i'@example.com"}'
done
# Expected: all responses cluster tightly around 2.0xx seconds

Step 2 — Test known valid user:

for i in $(seq 1 10); do
  curl -s -o /dev/null -w "%{time_total}\n" \
    -X POST http://localhost:3000/api/v1/@apostrophecms/login/reset-request \
    -H "Content-Type: application/json" \
    -d '{"email": "admin"}'
done
# Expected: response times differ from 2.0s baseline (faster with local SMTP, slower with remote SMTP)

Step 3 — Statistical comparison: The two distributions will show a measurable divergence. With a local mail server, valid-user responses typically complete in <500ms. With a remote SMTP server, valid-user responses may take 3-5+ seconds. Either way, the timing is distinguishable from the fixed 2000ms invalid-user delay.

Impact

  • Account enumeration: An unauthenticated attacker can determine whether a given username or email address has an account in the Apostrophe instance.
  • Credential stuffing preparation: Confirmed valid accounts can be targeted with credential stuffing attacks using breached password databases.
  • Phishing targeting: Knowledge of valid accounts enables targeted phishing campaigns against confirmed users.
  • No rate limiting: The absence of throttling on the reset endpoint allows high-speed automated enumeration.
  • Mitigating factor: The passwordReset option defaults to false (index.js:62), so only instances that explicitly enable password reset are affected.

Recommended Fix

Normalize all code paths to a constant minimum duration, ensuring the response time does not leak whether a user was found:

async resetRequest(req) {
  const MIN_RESPONSE_TIME = 2000;
  const startTime = Date.now();
  const site = (req.headers.host || '').replace(/:\d+$/, '');
  const email = self.apos.launder.string(req.body.email);
  if (!email.length) {
    throw self.apos.error('invalid', req.t('apostrophe:loginResetEmailRequired'));
  }
  let user;
  try {
    user = await self.getPasswordResetUser(req.body.email);
  } catch (e) {
    self.apos.util.error(e);
  }
  if (!user) {
    self.apos.util.error(
      `Reset password request error - the user ${email} doesn\`t exist.`
    );
  } else if (!user.email) {
    self.apos.util.error(
      `Reset password request error - the user ${user.username} doesn\`t have an email.`
    );
  } else {
    const reset = self.apos.util.generateId();
    user.passwordReset = reset;
    user.passwordResetAt = new Date();
    await self.apos.user.update(req, user, { permissions: false });
    let port = (req.headers.host || '').split(':')[1];
    if (!port || [ '80', '443' ].includes(port)) {
      port = '';
    } else {
      port = `:${port}`;
    }
    const parsed = new URL(
      req.absoluteUrl,
      self.apos.baseUrl
        ? undefined
        : `${req.protocol}://${req.hostname}${port}`
    );
    parsed.pathname = self.login();
    parsed.search = '?';
    parsed.searchParams.append('reset', reset);
    parsed.searchParams.append('email', user.email);
    try {
      await self.email(req, 'passwordResetEmail', {
        user,
        url: parsed.toString(),
        site
      }, {
        to: user.email,
        subject: req.t('apostrophe:passwordResetRequest', { site })
      });
    } catch (err) {
      self.apos.util.error(`Error while sending email to ${user.email}`, err);
    }
  }
  // Pad all paths to a constant minimum duration
  const elapsed = Date.now() - startTime;
  if (elapsed < MIN_RESPONSE_TIME) {
    await Promise.delay(MIN_RESPONSE_TIME - elapsed);
  }
},

Additionally, consider applying rate limiting to the reset-request endpoint to prevent high-speed enumeration attempts.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "apostrophe"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "4.29.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-33877"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-208"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-16T20:42:11Z",
    "nvd_published_at": "2026-04-15T20:16:35Z",
    "severity": "LOW"
  },
  "details": "## Summary\n\nThe password reset endpoint (`/api/v1/@apostrophecms/login/reset-request`) exhibits a measurable timing side channel that allows unauthenticated attackers to enumerate valid usernames and email addresses. When a user is not found, the handler returns after a fixed 2-second artificial delay, but when a valid user is found, it performs database writes and SMTP operations with no equivalent delay normalization, producing a distinguishable timing profile.\n\n## Details\n\nThe `resetRequest` handler in `modules/@apostrophecms/login/index.js` attempts to obscure the user-not-found path with an artificial delay, but fails to normalize the timing of the user-found path:\n\n**User not found \u2014 fixed 2000ms delay** (`index.js:309-314`):\n```javascript\nif (!user) {\n  await wait();  // wait = (t = 2000) =\u003e Promise.delay(t)\n  self.apos.util.error(\n    `Reset password request error - the user ${email} doesn\\`t exist.`\n  );\n  return;\n}\n```\n\n**User found \u2014 variable-duration DB + SMTP operations, no artificial delay** (`index.js:323-355`):\n```javascript\nconst reset = self.apos.util.generateId();\nuser.passwordReset = reset;\nuser.passwordResetAt = new Date();\nawait self.apos.user.update(req, user, { permissions: false });\n// ... URL construction ...\nawait self.email(req, \u0027passwordResetEmail\u0027, {\n  user,\n  url: parsed.toString(),\n  site\n}, {\n  to: user.email,\n  subject: req.t(\u0027apostrophe:passwordResetRequest\u0027, { site })\n});\n```\n\nThe user-found path includes a MongoDB `update()` call and an SMTP `email()` send, which together produce response times that differ measurably from the fixed 2000ms delay. Depending on SMTP server latency, responses for valid users will either be noticeably faster (local/fast SMTP) or slower (remote SMTP) than the constant 2-second delay for invalid users.\n\nAdditionally, the `getPasswordResetUser` method (`index.js:664-666`) accepts both username and email via an `$or` query, enabling enumeration of both identifiers:\n```javascript\nconst criteriaOr = [\n  { username: email },\n  { email }\n];\n```\n\nThere is no rate limiting on the reset endpoint. The `checkLoginAttempts` throttle (`index.js:978`) is only applied to the login flow, allowing unlimited rapid probing of the reset endpoint.\n\n## PoC\n\n**Prerequisites:** An Apostrophe instance with `passwordReset: true` enabled in `@apostrophecms/login` configuration.\n\n**Step 1 \u2014 Baseline invalid user timing:**\n```bash\nfor i in $(seq 1 10); do\n  curl -s -o /dev/null -w \"%{time_total}\\n\" \\\n    -X POST http://localhost:3000/api/v1/@apostrophecms/login/reset-request \\\n    -H \"Content-Type: application/json\" \\\n    -d \u0027{\"email\": \"nonexistent-user-\u0027$i\u0027@example.com\"}\u0027\ndone\n# Expected: all responses cluster tightly around 2.0xx seconds\n```\n\n**Step 2 \u2014 Test known valid user:**\n```bash\nfor i in $(seq 1 10); do\n  curl -s -o /dev/null -w \"%{time_total}\\n\" \\\n    -X POST http://localhost:3000/api/v1/@apostrophecms/login/reset-request \\\n    -H \"Content-Type: application/json\" \\\n    -d \u0027{\"email\": \"admin\"}\u0027\ndone\n# Expected: response times differ from 2.0s baseline (faster with local SMTP, slower with remote SMTP)\n```\n\n**Step 3 \u2014 Statistical comparison:**\nThe two distributions will show a measurable divergence. With a local mail server, valid-user responses typically complete in \u003c500ms. With a remote SMTP server, valid-user responses may take 3-5+ seconds. Either way, the timing is distinguishable from the fixed 2000ms invalid-user delay.\n\n## Impact\n\n- **Account enumeration:** An unauthenticated attacker can determine whether a given username or email address has an account in the Apostrophe instance.\n- **Credential stuffing preparation:** Confirmed valid accounts can be targeted with credential stuffing attacks using breached password databases.\n- **Phishing targeting:** Knowledge of valid accounts enables targeted phishing campaigns against confirmed users.\n- **No rate limiting:** The absence of throttling on the reset endpoint allows high-speed automated enumeration.\n- **Mitigating factor:** The `passwordReset` option defaults to `false` (`index.js:62`), so only instances that explicitly enable password reset are affected.\n\n## Recommended Fix\n\nNormalize all code paths to a constant minimum duration, ensuring the response time does not leak whether a user was found:\n\n```javascript\nasync resetRequest(req) {\n  const MIN_RESPONSE_TIME = 2000;\n  const startTime = Date.now();\n  const site = (req.headers.host || \u0027\u0027).replace(/:\\d+$/, \u0027\u0027);\n  const email = self.apos.launder.string(req.body.email);\n  if (!email.length) {\n    throw self.apos.error(\u0027invalid\u0027, req.t(\u0027apostrophe:loginResetEmailRequired\u0027));\n  }\n  let user;\n  try {\n    user = await self.getPasswordResetUser(req.body.email);\n  } catch (e) {\n    self.apos.util.error(e);\n  }\n  if (!user) {\n    self.apos.util.error(\n      `Reset password request error - the user ${email} doesn\\`t exist.`\n    );\n  } else if (!user.email) {\n    self.apos.util.error(\n      `Reset password request error - the user ${user.username} doesn\\`t have an email.`\n    );\n  } else {\n    const reset = self.apos.util.generateId();\n    user.passwordReset = reset;\n    user.passwordResetAt = new Date();\n    await self.apos.user.update(req, user, { permissions: false });\n    let port = (req.headers.host || \u0027\u0027).split(\u0027:\u0027)[1];\n    if (!port || [ \u002780\u0027, \u0027443\u0027 ].includes(port)) {\n      port = \u0027\u0027;\n    } else {\n      port = `:${port}`;\n    }\n    const parsed = new URL(\n      req.absoluteUrl,\n      self.apos.baseUrl\n        ? undefined\n        : `${req.protocol}://${req.hostname}${port}`\n    );\n    parsed.pathname = self.login();\n    parsed.search = \u0027?\u0027;\n    parsed.searchParams.append(\u0027reset\u0027, reset);\n    parsed.searchParams.append(\u0027email\u0027, user.email);\n    try {\n      await self.email(req, \u0027passwordResetEmail\u0027, {\n        user,\n        url: parsed.toString(),\n        site\n      }, {\n        to: user.email,\n        subject: req.t(\u0027apostrophe:passwordResetRequest\u0027, { site })\n      });\n    } catch (err) {\n      self.apos.util.error(`Error while sending email to ${user.email}`, err);\n    }\n  }\n  // Pad all paths to a constant minimum duration\n  const elapsed = Date.now() - startTime;\n  if (elapsed \u003c MIN_RESPONSE_TIME) {\n    await Promise.delay(MIN_RESPONSE_TIME - elapsed);\n  }\n},\n```\n\nAdditionally, consider applying rate limiting to the `reset-request` endpoint to prevent high-speed enumeration attempts.",
  "id": "GHSA-mj7r-x3h3-7rmr",
  "modified": "2026-04-16T20:42:11Z",
  "published": "2026-04-16T20:42:11Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-mj7r-x3h3-7rmr"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33877"
    },
    {
      "type": "WEB",
      "url": "https://github.com/apostrophecms/apostrophe/commit/e266cffd8c0d331a9b05c92bf11616556efcdc77"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/apostrophecms/apostrophe"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "ApostropheCMS: User Enumeration via Timing Side Channel in Password Reset Endpoint"
}

GHSA-MJC4-QQXC-7H36

Vulnerability from github – Published: 2026-05-20 21:31 – Updated: 2026-05-21 15:34
VLAI
Details

Crypt::SaltedHash versions through 0.09 for Perl is susceptible to timing attacks.

These versions use Perl's built-in eq comparison. Discrepencies in timing could be used to guess the underlying hash.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-47373"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-208"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-05-20T21:16:17Z",
    "severity": "HIGH"
  },
  "details": "Crypt::SaltedHash versions through 0.09 for Perl is susceptible to timing attacks.\n\nThese versions use Perl\u0027s built-in eq comparison. Discrepencies in timing could be used to guess the underlying hash.",
  "id": "GHSA-mjc4-qqxc-7h36",
  "modified": "2026-05-21T15:34:06Z",
  "published": "2026-05-20T21:31:33Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-47373"
    },
    {
      "type": "WEB",
      "url": "https://github.com/robrwo/perl-Crypt-SaltedHash/commit/c07bfc5c23185b0667233d0f2e1252d81f1f027a.patch"
    },
    {
      "type": "WEB",
      "url": "https://metacpan.org/release/RRWO/Crypt-SaltedHash-0.10/changes"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2026/05/20/21"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-MJGF-XJ26-9QF9

Vulnerability from github – Published: 2026-07-01 18:57 – Updated: 2026-07-01 18:57
VLAI
Summary
pay-rails/pay: non-constant-time HMAC comparison in Paddle Billing webhook signature verifier
Details

Summary

Pay::Webhooks::PaddleBillingController#valid_signature? (app/controllers/pay/webhooks/paddle_billing_controller.rb) verifies the Paddle Billing webhook signature by computing OpenSSL::HMAC.hexdigest(...) and comparing it to the attacker-supplied header value using Ruby's String#==. Ruby's == is non-constant-time — it returns as soon as the first byte mismatches — and exposes a per-byte timing side channel on the webhook signature verification path. The canonical mitigation is to use a constant-time primitive (OpenSSL.fixed_length_secure_compare / ActiveSupport::SecurityUtils.secure_compare).

Impact

  • CWE-208 — Observable Timing Discrepancy on the webhook signature verifier.
  • An attacker who can deliver requests to the /pay/webhooks/paddle_billing mount point can probe the verifier with guessed Paddle-Signature header values. Because String#== short-circuits on the first mismatching byte, the response-time distribution shifts as the prefix of the guess matches the real hex digest.
  • A signature recovered through the oracle lets the attacker deliver forged Paddle Billing webhook events (e.g. subscription.created / transaction.completed) against the host application. Pay's webhook processor enqueues a Pay::Webhooks::ProcessJob for any accepted webhook, which downstream applications use to update billing state — including provisioning paid features, recording refunds, and triggering customer notifications.
  • The endpoint is internet-reachable by definition (Paddle must POST events to it).

Affected versions

pay (rubygem) ≤ v11.6.1 (latest release as of 2026-05-27).

Vulnerable code (file:line)

app/controllers/pay/webhooks/paddle_billing_controller.rb:

24:      def valid_signature?(paddle_signature)
25:        return false if paddle_signature.blank?
26:
27:        ts_part, h1_part = paddle_signature.split(";")
28:        _, ts = ts_part.split("=")
29:        _, h1 = h1_part.split("=")
30:
31:        signed_payload = "#{ts}:#{request.raw_post}"
32:
33:        key = Pay::PaddleBilling.signing_secret
34:        data = signed_payload
35:        digest = OpenSSL::Digest.new("sha256")
36:
37:        hmac = OpenSSL::HMAC.hexdigest(digest, key, data)
38:        hmac == h1                          # <-- non-constant-time '=='
39:      end

hmac is the 64-character hex-encoded SHA-256 HMAC of "<ts>:<raw_post>" under the application's configured Paddle Billing signing secret. The comparison with h1 (the attacker-supplied h1= token from the Paddle-Signature header) uses Ruby's native String#==, which is implemented in MRI as rb_str_equal and returns immediately on the first byte mismatch.

How an attacker reaches this code

  1. Any Pay-using Rails application mounting Pay::Engine exposes POST /pay/webhooks/paddle_billing to the public internet (Paddle requires the endpoint to be reachable). The controller is configured by default in config/routes.rb when paddle_billing is enabled.
  2. The controller's before_action :verify_signature invokes valid_signature? on every inbound request.
  3. An attacker repeatedly POSTs forged webhook payloads with Paddle-Signature: ts=<now>;h1=<guess> headers and measures the response time. The verifier returns early on the first mismatching byte of the hex digest; with a sufficient probe count per byte position, response-time distribution reveals when the prefix of <guess> matches the real hmac.
  4. A signature recovered through the oracle lets the attacker forge arbitrary Paddle Billing webhook deliveries.

Proof of concept (microbenchmark)

Local Ruby microbenchmark isolating the verifier comparison path:

require 'openssl'
require 'benchmark'
require 'securerandom'

key = SecureRandom.hex(32)
payload = '1730000000:{"event_type":"transaction.completed"}'
real_hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, payload)
puts "real_hmac=#{real_hmac}"

def verify(real, guess)
  real == guess     # mirrors paddle_billing_controller.rb:38
end

guesses = {
  'all-wrong'    => ('0' * real_hmac.length),
  'match-1byte'  => real_hmac[0..0]  + '0' * (real_hmac.length - 1),
  'match-32byte' => real_hmac[0..31] + '0' * (real_hmac.length - 32),
  'match-63byte' => real_hmac[0..62] + '0',
  'exact-match'  => real_hmac.dup,
}
iters = 10_000_000
3.times { guesses.each_value { |g| 1_000_000.times { real_hmac == g } } }  # warmup
guesses.each do |label, g|
  t = Benchmark.realtime { iters.times { real_hmac == g } }
  puts "#{label.ljust(15)} avg_ns=#{(t * 1e9 / iters).round}"
end

This isolates the same String#== path used by valid_signature?. The static defect is verifiable by bundle show pay and reading line 38 of the controller.

End-to-end reproduction against gem install pay --version 11.6.1

Minimal Rails 8 app mounting Pay::Engine with paddle_billing enabled:

gem install rails -v 8.0.2
rails new payapp --skip-test --skip-bundle
cd payapp
echo "gem 'pay', '11.6.1'" >> Gemfile
echo "gem 'paddle', '~> 2.0'" >> Gemfile
bundle install
bin/rails g pay:install
# config/initializers/pay.rb adds Pay.setup, paddle_billing config
# config/routes.rb already has 'mount Pay::Engine => "/pay"' from generator

bin/rails server &

# attacker probes the webhook endpoint
WEBHOOK="http://127.0.0.1:3000/pay/webhooks/paddle_billing"
BODY='{"event_type":"transaction.completed","data":{}}'
TS=$(date +%s)
# Try guesses with different prefix-match counts; response-time delta is the oracle
for guess in 0000000000000000000000000000000000000000000000000000000000000000 \
             a000000000000000000000000000000000000000000000000000000000000000 ; do
  for _ in 1 2 3; do
    curl -s -w '%{time_total}\n' -o /dev/null \
      -X POST -H "Paddle-Signature: ts=$TS;h1=$guess" \
      -H 'Content-Type: application/json' -d "$BODY" "$WEBHOOK"
  done
done

The static defect is verifiable by:

$ bundle show pay
.../gems/pay-11.6.1
$ sed -n '38p' .../gems/pay-11.6.1/app/controllers/pay/webhooks/paddle_billing_controller.rb
        hmac == h1

After the fix is applied, the verifier uses ActiveSupport::SecurityUtils.secure_compare, which compares all bytes regardless of mismatch position, and the timing oracle closes.

Suggested fix

Replace == with ActiveSupport::SecurityUtils.secure_compare (Pay is a Rails engine, so ActiveSupport is always available).

       def valid_signature?(paddle_signature)
         return false if paddle_signature.blank?

         ts_part, h1_part = paddle_signature.split(";")
         _, ts = ts_part.split("=")
         _, h1 = h1_part.split("=")

         signed_payload = "#{ts}:#{request.raw_post}"

         key = Pay::PaddleBilling.signing_secret
         data = signed_payload
         digest = OpenSSL::Digest.new("sha256")

         hmac = OpenSSL::HMAC.hexdigest(digest, key, data)
-        hmac == h1
+        return false if h1.nil? || hmac.bytesize != h1.bytesize
+        ActiveSupport::SecurityUtils.secure_compare(hmac, h1)
       end

The bytesize-equality guard ensures secure_compare does not return early on a length mismatch (it falls back to == if lengths differ on older Rails versions). For the Paddle Billing signing format the hex tag is a fixed 64 chars.

Credit

Reported by tonghuaroot (https://github.com/tonghuaroot).

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "RubyGems",
        "name": "pay"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "11.6.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-208"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-01T18:57:02Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\n`Pay::Webhooks::PaddleBillingController#valid_signature?` (`app/controllers/pay/webhooks/paddle_billing_controller.rb`) verifies the Paddle Billing webhook signature by computing `OpenSSL::HMAC.hexdigest(...)` and comparing it to the attacker-supplied header value using Ruby\u0027s `String#==`. Ruby\u0027s `==` is non-constant-time \u2014 it returns as soon as the first byte mismatches \u2014 and exposes a per-byte timing side channel on the webhook signature verification path. The canonical mitigation is to use a constant-time primitive (`OpenSSL.fixed_length_secure_compare` / `ActiveSupport::SecurityUtils.secure_compare`).\n\n## Impact\n\n- **CWE-208** \u2014 Observable Timing Discrepancy on the webhook signature verifier.\n- An attacker who can deliver requests to the `/pay/webhooks/paddle_billing` mount point can probe the verifier with guessed `Paddle-Signature` header values. Because `String#==` short-circuits on the first mismatching byte, the response-time distribution shifts as the prefix of the guess matches the real hex digest.\n- A signature recovered through the oracle lets the attacker deliver forged Paddle Billing webhook events (e.g. `subscription.created` / `transaction.completed`) against the host application. Pay\u0027s webhook processor enqueues a `Pay::Webhooks::ProcessJob` for any accepted webhook, which downstream applications use to update billing state \u2014 including provisioning paid features, recording refunds, and triggering customer notifications.\n- The endpoint is internet-reachable by definition (Paddle must POST events to it).\n\n## Affected versions\n\n`pay` (rubygem) \u2264 v11.6.1 (latest release as of 2026-05-27).\n\n## Vulnerable code (file:line)\n\n`app/controllers/pay/webhooks/paddle_billing_controller.rb`:\n\n```ruby\n24:      def valid_signature?(paddle_signature)\n25:        return false if paddle_signature.blank?\n26:\n27:        ts_part, h1_part = paddle_signature.split(\";\")\n28:        _, ts = ts_part.split(\"=\")\n29:        _, h1 = h1_part.split(\"=\")\n30:\n31:        signed_payload = \"#{ts}:#{request.raw_post}\"\n32:\n33:        key = Pay::PaddleBilling.signing_secret\n34:        data = signed_payload\n35:        digest = OpenSSL::Digest.new(\"sha256\")\n36:\n37:        hmac = OpenSSL::HMAC.hexdigest(digest, key, data)\n38:        hmac == h1                          # \u003c-- non-constant-time \u0027==\u0027\n39:      end\n```\n\n`hmac` is the 64-character hex-encoded SHA-256 HMAC of `\"\u003cts\u003e:\u003craw_post\u003e\"` under the application\u0027s configured Paddle Billing signing secret. The comparison with `h1` (the attacker-supplied `h1=` token from the `Paddle-Signature` header) uses Ruby\u0027s native `String#==`, which is implemented in MRI as `rb_str_equal` and returns immediately on the first byte mismatch.\n\n## How an attacker reaches this code\n\n1. Any Pay-using Rails application mounting `Pay::Engine` exposes `POST /pay/webhooks/paddle_billing` to the public internet (Paddle requires the endpoint to be reachable). The controller is configured by default in `config/routes.rb` when `paddle_billing` is enabled.\n2. The controller\u0027s `before_action :verify_signature` invokes `valid_signature?` on every inbound request.\n3. An attacker repeatedly POSTs forged webhook payloads with `Paddle-Signature: ts=\u003cnow\u003e;h1=\u003cguess\u003e` headers and measures the response time. The verifier returns early on the first mismatching byte of the hex digest; with a sufficient probe count per byte position, response-time distribution reveals when the prefix of `\u003cguess\u003e` matches the real `hmac`.\n4. A signature recovered through the oracle lets the attacker forge arbitrary Paddle Billing webhook deliveries.\n\n## Proof of concept (microbenchmark)\n\nLocal Ruby microbenchmark isolating the verifier comparison path:\n\n```ruby\nrequire \u0027openssl\u0027\nrequire \u0027benchmark\u0027\nrequire \u0027securerandom\u0027\n\nkey = SecureRandom.hex(32)\npayload = \u00271730000000:{\"event_type\":\"transaction.completed\"}\u0027\nreal_hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new(\u0027sha256\u0027), key, payload)\nputs \"real_hmac=#{real_hmac}\"\n\ndef verify(real, guess)\n  real == guess     # mirrors paddle_billing_controller.rb:38\nend\n\nguesses = {\n  \u0027all-wrong\u0027    =\u003e (\u00270\u0027 * real_hmac.length),\n  \u0027match-1byte\u0027  =\u003e real_hmac[0..0]  + \u00270\u0027 * (real_hmac.length - 1),\n  \u0027match-32byte\u0027 =\u003e real_hmac[0..31] + \u00270\u0027 * (real_hmac.length - 32),\n  \u0027match-63byte\u0027 =\u003e real_hmac[0..62] + \u00270\u0027,\n  \u0027exact-match\u0027  =\u003e real_hmac.dup,\n}\niters = 10_000_000\n3.times { guesses.each_value { |g| 1_000_000.times { real_hmac == g } } }  # warmup\nguesses.each do |label, g|\n  t = Benchmark.realtime { iters.times { real_hmac == g } }\n  puts \"#{label.ljust(15)} avg_ns=#{(t * 1e9 / iters).round}\"\nend\n```\n\nThis isolates the same `String#==` path used by `valid_signature?`. The static defect is verifiable by `bundle show pay` and reading line 38 of the controller.\n\n## End-to-end reproduction against `gem install pay --version 11.6.1`\n\nMinimal Rails 8 app mounting `Pay::Engine` with `paddle_billing` enabled:\n\n```bash\ngem install rails -v 8.0.2\nrails new payapp --skip-test --skip-bundle\ncd payapp\necho \"gem \u0027pay\u0027, \u002711.6.1\u0027\" \u003e\u003e Gemfile\necho \"gem \u0027paddle\u0027, \u0027~\u003e 2.0\u0027\" \u003e\u003e Gemfile\nbundle install\nbin/rails g pay:install\n# config/initializers/pay.rb adds Pay.setup, paddle_billing config\n# config/routes.rb already has \u0027mount Pay::Engine =\u003e \"/pay\"\u0027 from generator\n\nbin/rails server \u0026\n\n# attacker probes the webhook endpoint\nWEBHOOK=\"http://127.0.0.1:3000/pay/webhooks/paddle_billing\"\nBODY=\u0027{\"event_type\":\"transaction.completed\",\"data\":{}}\u0027\nTS=$(date +%s)\n# Try guesses with different prefix-match counts; response-time delta is the oracle\nfor guess in 0000000000000000000000000000000000000000000000000000000000000000 \\\n             a000000000000000000000000000000000000000000000000000000000000000 ; do\n  for _ in 1 2 3; do\n    curl -s -w \u0027%{time_total}\\n\u0027 -o /dev/null \\\n      -X POST -H \"Paddle-Signature: ts=$TS;h1=$guess\" \\\n      -H \u0027Content-Type: application/json\u0027 -d \"$BODY\" \"$WEBHOOK\"\n  done\ndone\n```\n\nThe static defect is verifiable by:\n\n```\n$ bundle show pay\n.../gems/pay-11.6.1\n$ sed -n \u002738p\u0027 .../gems/pay-11.6.1/app/controllers/pay/webhooks/paddle_billing_controller.rb\n        hmac == h1\n```\n\nAfter the fix is applied, the verifier uses `ActiveSupport::SecurityUtils.secure_compare`, which compares all bytes regardless of mismatch position, and the timing oracle closes.\n\n## Suggested fix\n\nReplace `==` with `ActiveSupport::SecurityUtils.secure_compare` (Pay is a Rails engine, so ActiveSupport is always available).\n\n```diff\n       def valid_signature?(paddle_signature)\n         return false if paddle_signature.blank?\n \n         ts_part, h1_part = paddle_signature.split(\";\")\n         _, ts = ts_part.split(\"=\")\n         _, h1 = h1_part.split(\"=\")\n \n         signed_payload = \"#{ts}:#{request.raw_post}\"\n \n         key = Pay::PaddleBilling.signing_secret\n         data = signed_payload\n         digest = OpenSSL::Digest.new(\"sha256\")\n \n         hmac = OpenSSL::HMAC.hexdigest(digest, key, data)\n-        hmac == h1\n+        return false if h1.nil? || hmac.bytesize != h1.bytesize\n+        ActiveSupport::SecurityUtils.secure_compare(hmac, h1)\n       end\n```\n\nThe bytesize-equality guard ensures `secure_compare` does not return early on a length mismatch (it falls back to `==` if lengths differ on older Rails versions). For the Paddle Billing signing format the hex tag is a fixed 64 chars.\n\n## Credit\n\nReported by tonghuaroot (https://github.com/tonghuaroot).",
  "id": "GHSA-mjgf-xj26-9qf9",
  "modified": "2026-07-01T18:57:02Z",
  "published": "2026-07-01T18:57:02Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/pay-rails/pay/security/advisories/GHSA-mjgf-xj26-9qf9"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/pay-rails/pay"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "pay-rails/pay: non-constant-time HMAC comparison in Paddle Billing webhook signature verifier"
}

GHSA-MJQR-5C55-G77H

Vulnerability from github – Published: 2026-03-05 21:30 – Updated: 2026-03-06 23:11
VLAI
Summary
@perfood/couch-auth has an Observable Timing Discrepancy
Details

An Observable Timing Discrepancy in @perfood/couch-auth v0.26.0 allows attackers to access sensitive information via a timing side-channel.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@perfood/couch-auth"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "0.26.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-70949"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-208"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-06T23:11:41Z",
    "nvd_published_at": "2026-03-05T21:16:13Z",
    "severity": "HIGH"
  },
  "details": "An Observable Timing Discrepancy in @perfood/couch-auth v0.26.0 allows attackers to access sensitive information via a timing side-channel.",
  "id": "GHSA-mjqr-5c55-g77h",
  "modified": "2026-03-06T23:11:41Z",
  "published": "2026-03-05T21:30:49Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-70949"
    },
    {
      "type": "WEB",
      "url": "https://gist.github.com/0xHunterr/38aab644874ca9f4646524c5b01cfe5e"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/perfood/couch-auth"
    },
    {
      "type": "WEB",
      "url": "https://www.npmjs.com/package/@perfood/couch-auth"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "@perfood/couch-auth has an Observable Timing Discrepancy "
}

GHSA-MMPQ-5HCV-HF2V

Vulnerability from github – Published: 2026-04-08 00:07 – Updated: 2026-04-15 21:17
VLAI
Summary
Parse Server has a login timing side-channel reveals user existence
Details

Impact

The login endpoint response time differs measurably depending on whether the submitted username or email exists in the database. When a user is not found, the server responds immediately. When a user exists but the password is wrong, a bcrypt comparison runs first, adding significant latency. This timing difference allows an unauthenticated attacker to enumerate valid usernames.

Patches

A dummy bcrypt comparison is now performed when no user is found, normalizing response timing regardless of user existence. Additionally, accounts without a stored password (e.g. OAuth-only) now also run a dummy comparison to prevent the same timing oracle.

Workarounds

Configure rate limiting on the login endpoint to slow automated enumeration. This reduces throughput but does not eliminate the timing signal for individual requests.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "parse-server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "9.0.0"
            },
            {
              "fixed": "9.8.0-alpha.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "parse-server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "8.6.74"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-39321"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-208"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-08T00:07:10Z",
    "nvd_published_at": "2026-04-07T18:16:43Z",
    "severity": "MODERATE"
  },
  "details": "### Impact\n\nThe login endpoint response time differs measurably depending on whether the submitted username or email exists in the database. When a user is not found, the server responds immediately. When a user exists but the password is wrong, a bcrypt comparison runs first, adding significant latency. This timing difference allows an unauthenticated attacker to enumerate valid usernames.\n\n### Patches\n\nA dummy bcrypt comparison is now performed when no user is found, normalizing response timing regardless of user existence. Additionally, accounts without a stored password (e.g. OAuth-only) now also run a dummy comparison to prevent the same timing oracle.\n\n### Workarounds\n\nConfigure rate limiting on the login endpoint to slow automated enumeration. This reduces throughput but does not eliminate the timing signal for individual requests.",
  "id": "GHSA-mmpq-5hcv-hf2v",
  "modified": "2026-04-15T21:17:16Z",
  "published": "2026-04-08T00:07:10Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/parse-community/parse-server/security/advisories/GHSA-mmpq-5hcv-hf2v"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-39321"
    },
    {
      "type": "WEB",
      "url": "https://github.com/parse-community/parse-server/pull/10398"
    },
    {
      "type": "WEB",
      "url": "https://github.com/parse-community/parse-server/pull/10399"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/parse-community/parse-server"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Parse Server has a login timing side-channel reveals user existence"
}

No mitigation information available for this CWE.

CAPEC-462: Cross-Domain Search Timing

An attacker initiates cross domain HTTP / GET requests and times the server responses. The timing of these responses may leak important information on what is happening on the server. Browser's same origin policy prevents the attacker from directly reading the server responses (in the absence of any other weaknesses), but does not prevent the attacker from timing the responses to requests that the attacker issued cross domain.

CAPEC-541: Application Fingerprinting

An adversary engages in fingerprinting activities to determine the type or version of an application installed on a remote target.

CAPEC-580: System Footprinting

An adversary engages in active probing and exploration activities to determine security information about a remote target system. Often times adversaries will rely on remote applications that can be probed for system configurations.