CWE-208
AllowedObservable 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.
359 vulnerabilities reference this CWE, most recent first.
GHSA-MJ7R-X3H3-7RMR
Vulnerability from github – Published: 2026-04-16 20:42 – Updated: 2026-04-16 20:42Summary
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
passwordResetoption defaults tofalse(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.
{
"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:34Crypt::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.
{
"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:57Summary
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_billingmount point can probe the verifier with guessedPaddle-Signatureheader values. BecauseString#==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 aPay::Webhooks::ProcessJobfor 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
- Any Pay-using Rails application mounting
Pay::EngineexposesPOST /pay/webhooks/paddle_billingto the public internet (Paddle requires the endpoint to be reachable). The controller is configured by default inconfig/routes.rbwhenpaddle_billingis enabled. - The controller's
before_action :verify_signatureinvokesvalid_signature?on every inbound request. - 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 realhmac. - 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).
{
"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:11An Observable Timing Discrepancy in @perfood/couch-auth v0.26.0 allows attackers to access sensitive information via a timing side-channel.
{
"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:17Impact
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.
{
"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"
}
GHSA-MQ6F-5XH5-HGCF
Vulnerability from github – Published: 2023-10-10 21:29 – Updated: 2023-11-09 16:38In the Harbor jobservice container, the comparison of secrets in the authenticator type is prone to timing attacks. The vulnerability occurs due to the following code: https://github.com/goharbor/harbor/blob/aaea068cceb4063ab89313d9785f2b40f35b0d63/src/jobservice/api/authenticator.go#L69-L69 To avoid this issue, constant time comparison should be used.
subtle.ConstantTimeCompare([]byte(expectedSecret), []byte(secret)) == 0
Impact
This attack might be possible theoretically, but no workable proof of concept is available, and access complexity is set at High. The jobservice exposes these APIs
Create a job task --- POST /api/v1/jobs
Get job task information --- GET /api/v1/jobs/{job_id}
Stop job task --- POST /api/v1/jobs/{job_id}
Get job log task --- GET /api/v1/jobs/{job_id}/log
Get job execution --- GET /api/v1/jobs/{job_id}/executions
Get job stats --- GET /api/v1/stats
Get job service configuration --- GET /api/v1/config
It is used to create jobs/stop job tasks and retrieve job task information. If an attacker obtains the secrets, it is possible to retrieve the job information, create a job, or stop a job task.
The following versions of Harbor are involved: <=Harbor 2.8.2, <=Harbor 2.7.2, <= Harbor 2.6.x, <=Harbor 1.10.17
Patches
Harbor 2.8.3, Harbor 2.7.3, Harbor 1.10.18
Workarounds
Because the jobservice only exposes HTTP service to harbor-core containers, blocking any inbound traffic from the external network to the jobservice container can reduce the risk.
Credits
Thanks to Porcupiney Hairs for reporting this issue.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/goharbor/harbor"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.10.18"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Go",
"name": "github.com/goharbor/harbor"
},
"ranges": [
{
"events": [
{
"introduced": "2.0.0"
},
{
"fixed": "2.7.3"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Go",
"name": "github.com/goharbor/harbor"
},
"ranges": [
{
"events": [
{
"introduced": "2.8.0"
},
{
"fixed": "2.8.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2023-20902"
],
"database_specific": {
"cwe_ids": [
"CWE-208",
"CWE-362"
],
"github_reviewed": true,
"github_reviewed_at": "2023-10-10T21:29:02Z",
"nvd_published_at": "2023-11-09T01:15:07Z",
"severity": "MODERATE"
},
"details": "In the Harbor jobservice container, the comparison of secrets in the authenticator type is prone to timing attacks. The vulnerability occurs due to the following code: https://github.com/goharbor/harbor/blob/aaea068cceb4063ab89313d9785f2b40f35b0d63/src/jobservice/api/authenticator.go#L69-L69\nTo avoid this issue, constant time comparison should be used.\n```\nsubtle.ConstantTimeCompare([]byte(expectedSecret), []byte(secret)) == 0\n```\n\n### Impact\nThis attack might be possible theoretically, but no workable proof of concept is available, and access complexity is set at High.\nThe jobservice exposes these APIs\n```\nCreate a job task --- POST /api/v1/jobs \nGet job task information --- GET /api/v1/jobs/{job_id}\nStop job task --- POST /api/v1/jobs/{job_id}\nGet job log task --- GET /api/v1/jobs/{job_id}/log\nGet job execution --- GET /api/v1/jobs/{job_id}/executions\nGet job stats --- GET /api/v1/stats\nGet job service configuration --- GET /api/v1/config\n```\nIt is used to create jobs/stop job tasks and retrieve job task information. If an attacker obtains the secrets, it is possible to retrieve the job information, create a job, or stop a job task. \n\nThe following versions of Harbor are involved:\n\u003c=Harbor 2.8.2, \u003c=Harbor 2.7.2, \u003c= Harbor 2.6.x, \u003c=Harbor 1.10.17\n\n\n### Patches\nHarbor 2.8.3, Harbor 2.7.3, Harbor 1.10.18\n\n### Workarounds\nBecause the jobservice only exposes HTTP service to harbor-core containers, blocking any inbound traffic from the external network to the jobservice container can reduce the risk.\n\n### Credits\nThanks to Porcupiney Hairs for reporting this issue.\n",
"id": "GHSA-mq6f-5xh5-hgcf",
"modified": "2023-11-09T16:38:40Z",
"published": "2023-10-10T21:29:02Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/goharbor/harbor/security/advisories/GHSA-mq6f-5xh5-hgcf"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-20902"
},
{
"type": "PACKAGE",
"url": "https://github.com/goharbor/harbor"
},
{
"type": "WEB",
"url": "https://github.com/goharbor/harbor/blob/aaea068cceb4063ab89313d9785f2b40f35b0d63/src/jobservice/api/authenticator.go#L69-L69"
},
{
"type": "WEB",
"url": "https://github.com/goharbor/harbor/releases/tag/v1.10.18"
},
{
"type": "WEB",
"url": "https://github.com/goharbor/harbor/releases/tag/v2.7.3"
},
{
"type": "WEB",
"url": "https://github.com/goharbor/harbor/releases/tag/v2.8.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"
}
],
"summary": "Harbor timing attack risk"
}
GHSA-MQC9-9P37-H763
Vulnerability from github – Published: 2025-10-21 15:31 – Updated: 2025-10-21 18:30Mbed TLS through 3.6.4 has an Observable Timing Discrepancy.
{
"affected": [],
"aliases": [
"CVE-2025-59438"
],
"database_specific": {
"cwe_ids": [
"CWE-208"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-10-21T15:15:39Z",
"severity": "MODERATE"
},
"details": "Mbed TLS through 3.6.4 has an Observable Timing Discrepancy.",
"id": "GHSA-mqc9-9p37-h763",
"modified": "2025-10-21T18:30:33Z",
"published": "2025-10-21T15:31:00Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-59438"
},
{
"type": "WEB",
"url": "https://mbed-tls.readthedocs.io/en/latest/security-advisories/mbedtls-security-advisory-2025-10-invalid-padding-error"
},
{
"type": "WEB",
"url": "https://mbed-tls.readthedocs.io/en/latest/tech-updates/security-advisories"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-MV3X-9FW3-QF38
Vulnerability from github – Published: 2026-03-27 09:31 – Updated: 2026-06-30 03:36Doveadm credentials are verified using direct comparison which is susceptible to timing oracle attack. An attacker can use this to determine the configured credentials. Figuring out the credential will lead into full access to the affected component. Limit access to the doveadm http service port, install fixed version. No publicly available exploits are known.
{
"affected": [],
"aliases": [
"CVE-2026-27856"
],
"database_specific": {
"cwe_ids": [
"CWE-208",
"CWE-287"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-03-27T09:16:19Z",
"severity": "HIGH"
},
"details": "Doveadm credentials are verified using direct comparison which is susceptible to timing oracle attack. An attacker can use this to determine the configured credentials. Figuring out the credential will lead into full access to the affected component. Limit access to the doveadm http service port, install fixed version. No publicly available exploits are known.",
"id": "GHSA-mv3x-9fw3-qf38",
"modified": "2026-06-30T03:36:02Z",
"published": "2026-03-27T09:31:18Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-27856"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:26564"
},
{
"type": "WEB",
"url": "https://access.redhat.com/security/cve/CVE-2026-27856"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=2452171"
},
{
"type": "WEB",
"url": "https://documentation.open-xchange.com/dovecot/security/advisories/csaf/2026/oxdc-adv-2026-0001.json"
},
{
"type": "WEB",
"url": "https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-27856.json"
}
],
"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"
}
]
}
GHSA-MW4G-W7HH-RJPC
Vulnerability from github – Published: 2024-11-01 18:31 – Updated: 2024-11-01 18:31IBM TXSeries for Multiplatforms 10.1 could allow an attacker to determine valid usernames due to an observable timing discrepancy which could be used in further attacks against the system.
{
"affected": [],
"aliases": [
"CVE-2024-41741"
],
"database_specific": {
"cwe_ids": [
"CWE-203",
"CWE-208"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-11-01T17:15:16Z",
"severity": "MODERATE"
},
"details": "IBM TXSeries for Multiplatforms 10.1 could allow an attacker to determine valid usernames due to an observable timing discrepancy which could be used in further attacks against the system.",
"id": "GHSA-mw4g-w7hh-rjpc",
"modified": "2024-11-01T18:31:33Z",
"published": "2024-11-01T18:31:33Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-41741"
},
{
"type": "WEB",
"url": "https://www.ibm.com/support/pages/node/7174572"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-MXCC-7H5M-X57R
Vulnerability from github – Published: 2022-07-28 00:00 – Updated: 2024-01-05 13:44Jenkins GitHub Plugin 1.34.4 and earlier uses a non-constant time comparison function when checking whether the provided and computed webhook signatures are equal, allowing attackers to use statistical methods to obtain a valid webhook signature. GitHub Plugin 1.34.5 uses a constant-time comparison when validating the webhook signature.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.34.4"
},
"package": {
"ecosystem": "Maven",
"name": "com.coravy.hudson.plugins.github:github"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.34.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2022-36885"
],
"database_specific": {
"cwe_ids": [
"CWE-203",
"CWE-208"
],
"github_reviewed": true,
"github_reviewed_at": "2022-08-24T20:44:20Z",
"nvd_published_at": "2022-07-27T15:15:00Z",
"severity": "LOW"
},
"details": "Jenkins GitHub Plugin 1.34.4 and earlier uses a non-constant time comparison function when checking whether the provided and computed webhook signatures are equal, allowing attackers to use statistical methods to obtain a valid webhook signature. GitHub Plugin 1.34.5 uses a constant-time comparison when validating the webhook signature.\n\n",
"id": "GHSA-mxcc-7h5m-x57r",
"modified": "2024-01-05T13:44:46Z",
"published": "2022-07-28T00:00:43Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-36885"
},
{
"type": "WEB",
"url": "https://github.com/jenkinsci/github-plugin/commit/11d1d79ebf85248dc43432389746c1ecc3452b6a"
},
{
"type": "PACKAGE",
"url": "https://github.com/jenkinsci/github-plugin"
},
{
"type": "WEB",
"url": "https://github.com/jenkinsci/github-plugin/releases/tag/v1.34.5"
},
{
"type": "WEB",
"url": "https://plugins.jenkins.io/github-issues"
},
{
"type": "WEB",
"url": "https://www.jenkins.io/security/advisory/2022-07-27/#SECURITY-1849"
},
{
"type": "WEB",
"url": "http://www.openwall.com/lists/oss-security/2022/07/27/1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Jenkins GitHub plugin uses weak webhook signature function"
}
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.