Repository navigation
Update dependency phpseclib/phpseclib to ^3.0.57 [SECURITY] - #80
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
renovate
Bot
force-pushed
the
renovate/packagist-phpseclib-phpseclib-vulnerability
branch
from
April 11, 2026 01:32
3bb5c02 to
ae1e78d
Compare
renovate
Bot
force-pushed
the
renovate/packagist-phpseclib-phpseclib-vulnerability
branch
from
May 6, 2026 18:23
ae1e78d to
a9bbcea
Compare
renovate
Bot
force-pushed
the
renovate/packagist-phpseclib-phpseclib-vulnerability
branch
from
June 20, 2026 05:10
a9bbcea to
8693bf8
Compare
| datasource | package | from | to | | ---------- | ------------------- | ------ | ------ | | packagist | phpseclib/phpseclib | 3.0.37 | 3.0.57 | Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
renovate
Bot
force-pushed
the
renovate/packagist-phpseclib-phpseclib-vulnerability
branch
from
October 3, 2026 12:15
8693bf8 to
c0ab3b7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
^3.0.37→^3.0.57phpseclib's AES-CBC unpadding susceptible to padding oracle timing attack
CVE-2026-32935 / GHSA-94g3-g5v7-q4jg
More information
Details
Impact
Those using AES in CBC mode may be susceptible to a padding oracle timing attack.
Patches
phpseclib/phpseclib@ccc21ae
Workarounds
Use AES in CTR, CFB or OFB modes
References
phpseclib/phpseclib@ccc21ae
Severity
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
phpseclib has a variable-time HMAC comparison in SSH2::get_binary_packet() using != instead of hash_equals()
CVE-2026-40194 / GHSA-r854-jrxh-36qx
More information
Details
phpseclib SSH2: Variable-time comparison in HMAC verification
Summary
phpseclib\Net\SSH2::get_binary_packet()uses PHP's!=operator to compare a received SSH packet HMAC against the locally computed HMAC.!=on equal-length binary strings in PHP usesmemcmp(), which short-circuits on the first differing byte. This is a real variable-time comparison (CWE-208), proven by scaling benchmarks.The finding is Low severity (defense-in-depth), not Critical. Practical exploitation over the network is prevented by SSH's disconnect-on-MAC-failure behavior combined with per-connection session keys. The fix is a one-liner: replace
!=withhash_equals(), which the codebase already uses in 9 other places.phpseclib/phpseclibphpseclib/Net/SSH2.phpe819a163c): 3405 and 3410CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:Nmaster,3.0,2.0,1.0(all supported versions)Root cause
phpseclib/Net/SSH2.phplines 3399-3415 (master ate819a163c):Both
$hmac(read from the socket viaStrings::pop($raw, $this->hmac_size)at line 3348) and the computed hash are equal-length binary strings. PHP's!=operator on equal-length strings dispatches tozend_binary_strcmp()which internally callsmemcmp(). Modern libcmemcmpshort-circuits on the first differing byte, producing a timing signal that scales linearly with the number of matching leading bytes.The same bug exists on every supported branch
master(@e819a163c)phpseclib/Net/SSH2.php$hmac != $this->hmac_check->hash(...)3.0phpseclib/Net/SSH2.php$hmac != $this->hmac_check->hash(...)2.0phpseclib/Net/SSH2.php$hmac != $this->hmac_check->hash(...)1.0phpseclib/Net/SSH2.php$hmac != $this->hmac_check->hash(...)Verified with
git show <branch>:phpseclib/Net/SSH2.php.Reachability
The HMAC verification path at lines 3399-3415 is reached on every received SSH packet when the negotiated cipher is not AEAD — i.e., any of:
aes128-cbc,aes192-cbc,aes256-cbcaes128-ctr,aes192-ctr,aes256-ctr3des-cbc,3des-ctrblowfish-cbc,blowfish-ctrtwofish-*-cbc,twofish-*-ctrarcfour,arcfour128,arcfour256combined with any non-AEAD MAC (hmac-sha2-256, hmac-sha2-512, hmac-sha1, hmac-sha1-96, hmac-md5, hmac-md5-96, umac-64, umac-128, and their -etm variants — full list at
getSupportedMACAlgorithms()around line 4754).AEAD ciphers (
aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com) go through a different path (lines 3353-3381) and do not reach the!=comparison — their authentication tag is checked inside the AEAD implementation.$this->hmac_checkis set during key exchange atSSH2.php:1812:So any SSH2 client session negotiating a non-AEAD cipher exercises the vulnerable code path starting from the first post-KEX packet.
Contrast with existing code that does the right thing
hash_equals()is already used in 9 other places in the phpseclib codebase, proving the maintainer knows the pattern:The SSH2 MAC comparison is the one place it was missed.
Proof of concept
All scripts under
poc/. They prove three things:!=on equal-length binary strings IS variable-time and short-circuits.hash_equals()is constant-time over the same inputs.Crypt\Hashclass, the code path atSSH2.php:3405/3410leaks ~2-14 ns of signal per comparison.PoC 1: baseline measurement (
poc/01_verify_variable_time.php)Measures
!=vshash_equals()on 32-byte strings with first-byte vs last-byte mismatch. 500,000 iterations each, trimmed mean, Welch's t-test.Representative output (PHP 8.3.6 on Linux x86_64):
The 32-byte delta is small (~2 ns) and lives in the noise. This alone doesn't prove variable-time behavior. PoC 2 does.
PoC 2: scaling test (
poc/02_scaling_test.php)The decisive test. If
!=is truly constant-time, timing should not depend on mismatch position. If it short-circuits, timing should scale linearly with prefix length. Test strings from 32 bytes to 4096 bytes.This is monotone, linear scaling. Confirmed variable-time:
!=short-circuits. Per-byte delta ≈ 0.089 ns/byte (4096 bytes → ~284 ns → 284/4096 ≈ 0.069 ns/byte after accounting for the fixed call overhead).PoC 3: contrast with
hash_equals()(poc/03_hash_equals_scaling.php)Same test,
!=vshash_equals()on 1024-byte strings:!=monotonically increases with mismatch position.hash_equalsis flat; the 31 ns range is measurement jitter (sd ≈ 90 ns > range).Per-byte delta from this run: 91.62 / 1023 ≈ 0.089 ns/byte. Extrapolated to 32-byte HMAC: ~2.86 ns total signal between first-byte-diff and last-byte-diff.
PoC 4: end-to-end with phpseclib's Hash class (
poc/04_phpseclib_in_context.php)Uses
phpseclib4\Crypt\Hash(the exact class bound to$this->hmac_check) to compute a real HMAC-SHA-256, then executes the exact expression$hmac != $this->hmac_check->hash(...)from SSH2.php:3410:The 13.86 ns vs 2.86 ns variation between runs is within measurement variance — the signal is real and the sign matches every run (last > first for
!=, flat forhash_equals).Impact
Severity: Low (defense-in-depth). This is a real CWE-208 instance, but it is not a practical remote vulnerability.
Why exploitation over the network is infeasible
Signal is tiny. ~3-14 ns per HMAC compare. Network RTT jitter on a reasonable LAN is 100 µs (100,000 ns). On the internet it is 1-10 ms. The signal-to-noise ratio for a remote observer is ~1e-5 to 1e-7.
One measurement per connection. On every MAC failure,
SSH2.php:3406/3411callsdisconnect_helper(MAC_ERROR)and throws. The connection is torn down. A reconnect goes through a fresh key exchange, producing a new HMAC key. The HMAC over a fresh key is uncorrelated with the prior HMAC. An attacker cannot accumulate prefix-matching information across connections because there is no fixed target.MAC is over sequence-numbered data. Each packet's MAC input includes
$this->get_seq_no(line 3410) or a nonce (line 3404). Even within a single connection, replays are impossible — every MAC is bound to a distinct seq number. There is no stable oracle to probe.No adaptive probe. A Bleichenbacher/Lucky13-style attack needs tens of thousands of adaptive queries against a single secret. SSH's hard disconnect eliminates this primitive entirely.
Rough sample-count requirement. To distinguish a 3 ns signal from 1 ms jitter with high confidence you need roughly
(noise / signal)^2 ≈ (1e6 / 3)^2 ≈ 1e11independent samples. With one sample per connection (and each connection being against a fresh secret), this is unreachable on any practical scale.What remains real
hash_equals()in 9 other comparable places. The SSH2 MAC path is inconsistent with the rest of the codebase.memcmp()implementation changes, or if a future MAC algorithm uses longer digests, the signal grows linearly. A constant-time comparison eliminates the concern permanently.js/timing-attack, phpcs-security-audit) flag non-constant-time comparisons on secret values. A clean codebase passes those checks.CVSS breakdown
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N= 3.7 (Low)This is the same vector string used for CVE-2026-32935 (the recent AES-CBC padding oracle in the same library), differing only in Confidentiality (L vs H) because padding oracle actually recovers plaintext whereas this MAC timing does not enable any known plaintext recovery.
Fix
Two-line patch:
The same one-liner should be applied to
phpseclib/Net/SSH2.phpon3.0(lines 3741, 3746),2.0(line 3796), and1.0(line 3810).hash_equals()is a built-in PHP function available since PHP 5.6. phpseclib's minimum PHP version for all supported branches is well above that, so there is no compatibility concern — and indeed, as noted,hash_equals()is already used throughout the codebase for exactly this purpose.PoC 5: prefix-position byte-recovery test (
poc/05_prefix_position_test.php)The critical question: can an attacker recover the MAC byte-by-byte? Tests candidate MACs with 0, 1, 2, ..., 31 correct leading bytes using phpseclib's
Crypt\Hashclass. 500,000 iterations per position.No monotonic signal. The range is negative (wrong direction for an oracle), and the standard deviations (4-5 ns) are larger than any observed position-dependent delta. Byte-by-byte MAC recovery is not feasible even locally on 32-byte HMACs.
This conclusively rules out the "timing oracle for MAC forgery" escalation path.
Escalation angles investigated and ruled out
9 rounds of external review suggested various escalation paths. Every one is dead:
Devil's advocate review
Every objection I could think of, addressed:
"This is just a code smell. Show me a real exploit."
Acknowledged. The report does not claim remote exploitability. It is submitted as Low / defense-in-depth, consistent with how similar findings are treated in other cryptographic libraries (see e.g. OpenSSH commits replacing
memcmp()withtimingsafe_bcmpfor similar reasons)."PHP's
!=might not even short-circuit — you're just seeing noise."PoC 2 rules this out. Timing scales monotonically and nearly linearly with mismatch position across a 128x range of string lengths (32 bytes → 4096 bytes). That is not noise.
"SSH disconnects on MAC failure, so you only get one sample. Case closed."
Correct, and the report says so explicitly. This is why the severity is Low rather than High.
"There is no adaptive probe because session keys rotate per connection."
Correct, and the report says so explicitly. This is the primary reason the network attack is infeasible.
"Is this Lucky13-style? Does the MAC check happen after padding?"
No. MAC verification at
SSH2.php:3399-3414runs BEFORE any padding interpretation. Padding length is only read at line 3418, strictly after the MAC check. There is no unpadding oracle here. (Lucky13 in phpseclib's AES-CBC decrypt path is a separate, already-fixed issue — CVE-2026-32935.)"Maybe the
get_seq_noor nonce path somehow enables forgery."No. The seq number is deterministic (incremented by one per packet) and the nonce (for UMAC) is derived from it. Neither is attacker-controlled in any useful sense. The MAC key is the secret, and it is per-connection.
"Is this already reported?"
Searched published advisories (
gh api repos/phpseclib/phpseclib/security-advisories). Only CVE-2026-32935 is published, for a different code path (AES-CBC padding oracle after MAC verification). The SSH2 MAC comparison issue is not covered by any published advisory. The fix commit for CVE-2026-32935 (ccc21aef71eb170e9bf819b167e67d1fd9e6e788) did not touchSSH2.php:3405or:3410."Does the project's threat model even consider this in scope?"
SECURITY.md says "To report a security vulnerability, please use the Tidelift security contact." It does not exclude timing side-channels. The maintainer has already fixed constant-time comparison issues in other parts of the library (RSA, OAEP, AES-CBC padding) and uses
hash_equals()in 9 other places. This issue is consistent with the maintainer's existing security posture, and the fix is trivial."Is the Low severity appropriate? Could it be informational?"
Low is appropriate. "Informational" would be for something that is provably no-impact. This is a demonstrable CWE-208 instance on secret-dependent data. It scores 3.7 under CVSS v3.1 with AC:H and C:L. That is Low, not Informational. The recent CVE-2026-32935 is assigned for a timing-side-channel in the same file category and given CVSS 4.0 = 8.2 (their vector is more severe because padding oracle actually recovers plaintext). This one would score lower than that, which matches the Low designation.
"Would a maintainer just close this?"
Unlikely. The fix is a one-liner, there is no behavioral change, it aligns with existing code style in the same codebase, and it silences future audit findings. The maintainer has a track record of accepting similar hardening patches. If they close it, the reason would likely be "we already know, not worth a CVE" — which would still leave the code fixed, which is the goal.
Suggested reporting path
GitHub PVR is enabled for phpseclib/phpseclib (
{"enabled":true}). SECURITY.md says to use the Tidelift contact, but GitHub PVR is a reasonable alternative and lets the maintainer decide whether to coordinate with Tidelift. Either path is acceptable.Given the Low severity, this could also be filed as a public pull request rather than a security advisory. That would be faster for everyone and avoids using the private disclosure channel for a hardening fix.
Files
poc/01_verify_variable_time.php— baseline 32-byte timing measurementpoc/02_scaling_test.php— decisive scaling test across string lengthspoc/03_hash_equals_scaling.php—!=vshash_equals()comparisonpoc/04_phpseclib_in_context.php— end-to-end repro using phpseclib'sCrypt\Hashnotes.md— research notes, dead ends, escalation angles consideredprior-work.md— prior work search resultsstatus.json— machine-readable statusKoda Reef
Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
phpseclib has a CVE-2024-27355 mitigation bypass — OID amplification DoS in ASN1::decodeOID()
CVE-2026-44167 / GHSA-3qpq-r242-jqj7
More information
Details
Impact
Anyone loading untrusted ASN1 files (eg. X509 certificates, RSA PKCS8 private or public keys, etc)
Patches
phpseclib/phpseclib@d53d202
Workarounds
No.
References
phpseclib/phpseclib@d53d202
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
phpseclib: X.509 certificate validation sends attacker-controlled outbound requests (server-side request forgery) via Authority Information Access
CVE-2026-55599 / GHSA-m557-wrgg-6rp4
More information
Details
Summary
When an application validates an untrusted X.509 certificate with phpseclib, X509::validateSignature() reads a URL out of that certificate's Authority Information Access (AIA) extension and connects to it. Attacker who supplies certificate fully controls host, port, and path of that connection. URL fetching is enabled by default, and no destination is blocked. An unauthenticated attacker can therefore make a validating server open connections to internal hosts and ports it should never reach, for example loopback 127.0.0.1, cloud metadata address 169.254.169.254, and internal-only services. This is a server-side request forgery (SSRF) caused by an insecure default. It is reproducible on current released LTS 3.0.53 and on 4.0 development line.
Details
When no already-trusted certificate authority is the issuer of certificate under validation, validateSignatureCountable() continues to AIA fetching. Default for validateSignature() is caonly = true:
testForIntermediate() takes URL straight out of certificate's AIA caIssuers field and fetches it. Value comes directly from certificate content and is never restricted:
fetchURL() connects to attacker host and port. There is no destination validation: no block on loopback, link-local, private, or metadata ranges, and no port restriction:
Fetching is on by default:
Same default-enabled logic exists in released 3.0.x. In 3.0.53 it sits at $disable_url_fetch = false on line 255 and fsockopen($parts['host'], ...) on line 1136 of phpseclib/File/X509.php.
Why this is a vulnerability and not merely a feature. AIA chasing is a legitimate capability described by RFC 4325, and this report does not claim fetching is wrong in itself. Vulnerability is the combination of three properties that together match definition of SSRF:
Reachability is not narrow. Fetch triggers whenever certificate's issuer is not already trusted, which an attacker arranges trivially by choosing any issuer name that is not in trust store. Having certificate authorities loaded does not protect a target: an attacker certificate that claims an unknown issuer still reaches testForIntermediate().
Response handling is blind. Fetched body is used only if it parses as a certificate, and is otherwise discarded, so an attacker does not directly read internal responses through this path. That limits confidentiality impact but does not remove request-forgery and reconnaissance capability.
PoC
Two reproductions follow: current released LTS 3.0.53, and 4.0 development line. Malicious certificate is plain PEM and is identical for both, since certificate format is the same across versions.
Build malicious certificate once (this uses 4.0 to build, but any tool that emits an X.509 certificate with an AIA caIssuers URL works):
Stand up a listener that represents an internal service on a port that is not otherwise reachable from outside:
Reproduction on released LTS 3.0.53. Install it and have an application validate certificate:
Reproduction on 4.0 development line. Same certificate, 4.0 namespace:
Observed result, on both 3.0.53 and 4.0.x-dev. Listener receives a request whose host, port, and path all come from certificate, even though validateSignature() returns false:
This was also confirmed end to end over HTTP: an unauthenticated POST of certificate to an endpoint that calls loadX509() then validateSignature() makes server connect outbound to attacker-chosen 127.0.0.1:19090. Changing host and port in certificate reaches any internal address and port, for example 169.254.169.254 or 127.0.0.1:6379.
Negative control. With X509::disableURLFetch() set before validation, validation returns false and no outbound connection is made. This confirms both root cause and that default-on behaviour is the trigger.
Impact
This is a server-side request forgery (CWE-918) caused by an insecure default (CWE-276): URL fetching is enabled by default and applies no destination restrictions while acting on untrusted certificate content.
An application is affected when it validates an attacker-influenced certificate, which covers client-certificate checks implemented in PHP, S/MIME and CMS signer verification, document and code-signing validation, and any feature that verifies an uploaded or pasted certificate. No authentication and no user interaction are needed.
What an attacker gains:
Because fetch is blind, an attacker does not read internal response bodies through this path directly, so this is a request-forgery and reconnaissance primitive rather than direct disclosure of internal data. Any reflective sink elsewhere in an application, or any internal endpoint that performs an action on a GET, increases real impact.
Suggested fix, strongest first: default disableURLFetch to true so AIA chasing is opt-in; if it stays enabled, validate destinations inside fetchURL() by rejecting loopback, link-local, and private addresses and restricting ports, and add an egress policy callback similar to existing setCRLLookupCallback(); and state plainly in documentation that validating an untrusted certificate can cause outbound requests to URLs found inside that certificate.
Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
phpseclib — non-constant-time X25519 scalar multiplication permits full private-key recovery
CVE-2026-84308 / GHSA-q97c-8qh3-fpc6
More information
Details
The pure-PHP X25519 scalar multiplication in phpseclib is not constant-time. Field addition and subtraction each perform a data-dependent conditional modular reduction, so the cost of each Montgomery-ladder step is a linear function of that step's reduction count which is a quantity determined by the secret scalar's prefix.
An observer with per-ladder-step resolution recovers the 251-bit clamped private scalar. This is a per-step leak, not an aggregate one: an instrumented code proof-of-concept recovers 20/20 test keys from 32 observed operations, and an observer that counts libgmp calls instead of timing them recovers a key from a single operation.
This is not a low-order-input issue. Recovery works with the RFC 7748 base point
u = 9, with no attacker-chosen input at all. Rejecting low-order public values does not close it.2. Affected component
Confirmed on phpseclib 3.0.56 (338 files under
phpseclib/,sha256(sorted(relpath NUL file_sha256 LF)) = cc7250b611f520e809131aab0931503457c44d8cbfb10d535251c6fec5f62a2b).The code appears unchanged across the 3.0 series wherever Curve25519 is supported, please confirm the affected range.
Math/PrimeField/Integer.php:189add()— conditionalsubtract($modulo)when the sum ≥ pMath/PrimeField/Integer.php:207subtract()— conditionaladd($modulo)when the result is negativeCrypt/EC/BaseCurves/Montgomery.php:229–234doubleAndAddPointCrypt/EC/Formats/Keys/MontgomeryPrivate.php:66multiplyPoint(getBasePoint(), dA)— no engine check of any kindCrypt/EC/Formats/Keys/PKCS8.php:194–200MontgomeryPrivateis missing3. Technical description
Operation counts in the ladder are already constant — 10 field multiplications, 4 additions and 4 subtractions per step, 2560 multiplications per 256-step ladder. Operand values are not. Each
PrimeField\Integer::add()/subtract()takes a data-dependent branch costing ~0.85–1.0 µs on the GMP engine, against a ~32 µs step period, so per-step cost isα + β·cwherecis that step's conditional-reduction count. Measured across 20 keys: R² = 0.91–0.98, β = 838–920 ns.cdepends on the whole scalar prefix, not on the current bit, so per-step thresholding is useless — it saturates at ~93% per bit foru = p−1and at chance foru = 9, and recovers 0/20 keys either way, because the bit string is a prefix-XOR in which one flipped step inverts the entire tail. Conditioning on the prefix removes the ambiguity: a beam search replays both branches from each candidate ladder state, reads off the exactcfor each, and scores against the observation. The victim's public key adjudicates the small residual search.Two facts bound the problem and are worth stating precisely, because they determine whether a fix is needed at all:
T(k)has exact entropyH(T) = 4.0357bits over clamped scalars, so a noiseless transition-count oracle still leaves ~2^247 candidates. The summed reduction countΣcis richer (~6.6–6.9 bits) and still leaves ~2^244. Any measurement that collapses the call to one number is safe. Per-step measurement is not.__gmpz_add = 4 + csub,__gmpz_sub = 4 + cadd,__gmpz_mul = __gmpz_mod = 10. Verified by differencing gdb breakpoint counts against phpseclib's owndoubleAndAddPoint— 27/27 steps exact, extended independently to 64/64 and 38/38 by our two reviewers. An observer that only counts these calls needs no timing, no calibration and no repetition.Results, 20 keys × 3 sampling seeds, 800 traces per path collected from 800 distinct PHP processes (so the observations are cross-process, as real requests would be):
u = 9u = p−1The model underlying the decoder is validated against the pinned implementation: 254/254 (key, peer) outputs match the real
DH::computeSecret, and all four RFC 7748 §6.1 vectors match both phpseclib and the published constants.Negative controls are clean — wrong public key, shuffled trace, wrong peer value, foreign key: 0/20 in every case. Nothing derived from the private key reaches the decoder; its inputs are the observation vector, the peer value, the victim's public key, and the public clamping constants.
5. Impact
In the instrumented, local model, recovery of the clamped scalar gives a permanent compromise of the X25519 private key. Clamping is applied on every call, so the recovered value is what every past and future operation with that key uses.
6. Restrictions
Required for exploitation:
EC::loadFormat('MontgomeryPrivate', $raw32)runs the ladder in everyconfiguration — but the format declares
IS_INVISIBLE(MontgomeryPrivate.php:40),so
PublicKeyLoader::loadskips it and nothing inside phpseclib calls it. Anapplication must name the format explicitly.
PKCS8/PublicKeyLoader::load/EC::createKeyrun the ladder only when ext-sodium is absent —PKCS8.php:194gates onsodium_crypto_box_publickey_from_secretkey. OpenSSL does not help here.DH::computeSecretruns the ladder only underEC::forceEngine('PHP'), or when bothopenssl_pkey_derive(DH.php:325) andsodium_crypto_scalarmult(EC/PrivateKey.php:75) are unavailable. ext-sodium is bundled and enabled by default in PHP 7.2+, so the reachable configurations are a minority — thoughdisable_functionshardening and--disable-sodiumbuilds do occur, particularly in shared hosting.libgmp.somapping —__gmpz_add/__gmpz_subare the correct targets;__gmpn_*are not, being size-dispatched internals).Not demonstrated — stated so it is not found rather than disclosed:
hrtime()hook insideMontgomery::multiplyPoint(one file differs from the pinned tree;Math/PrimeField/Integer.php, which carries the leak, is byte-identical). This models an observer with intra-call resolution; it is not itself an attacker capability. We note that the requisite primitives are present on ordinary hardware — on our test hostclflushandrdtscpwork, with 187–312 cycle cached/flushed separation — but building and validating a spy is separate work we have not done.Accordingly we report this as a hardening issue and demonstrated side channel, not as a completed remote exploit.
8. Suggested remediation
Integer.php:189and:207remain operand-dependent.MontgomeryPrivate.php:66the wayPKCS8.php:194–200already is. That is a one-block change and it closes the only entry point that is un-gated in every configuration. Keep the$curve instanceof Curve25519guard —MontgomeryPrivatealso accepts Curve448 keys.PKCS8::loadECDH, or fail closed, so stacks without ext-sodium do not fall through to the ladder.u = 9.Contact
George Stergiopoulos
Assistant Professor of cybersecurity
Athens University of Economics and Business, Greece
E: geostergiop@aueb.gr | s: https://www.aueb.gr/en/faculty_page/stergiopoulos-georgios
Severity
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:NReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
Release Notes
phpseclib/phpseclib (phpseclib/phpseclib)
v3.0.57Compare Source
v3.0.56Compare Source
v3.0.55Compare Source
v3.0.54Compare Source
v3.0.53Compare Source
v3.0.52Compare Source
v3.0.51Compare Source
v3.0.50Compare Source
v3.0.49Compare Source
v3.0.48Compare Source
v3.0.47Compare Source
v3.0.46Compare Source
v3.0.45Compare Source
v3.0.44Compare Source
v3.0.43Compare Source
Configuration
📅 Schedule: (in timezone UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
Read more information about the use of Renovate Bot within Laminas.