A Perl session library was seeding SHA-1 with `rand()`, epoch time, and PID — none of which are secrets
HTTP::Session (Ktat's CPAN module) before 0.54 uses HTTP::Session::ID::SHA1 by default, which builds session identifiers by SHA-1-hashing the output of Perl's non-cryptographic rand(), the high-resolution epoch time, and the process ID. None of those inputs are unpredictable to a remote attacker: rand() is a deterministic LCG seeded once at interpreter start, epoch time leaks via the Date: response header, and PIDs live in a tiny space (typically <65k on Linux, <99k on modern kernels). The sibling HTTP::Session::ID::MD5 has the same defect. Affected: all versions before 0.54. Fixed: 0.54 (uses Crypt::URandom / /dev/urandom).
Vendor severity is *wrong*. The 9.8 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H is auto-generated boilerplate that ignores attacker work factor and victim selection. To exploit this, an attacker has to: identify a *target* session (a specific authenticated user), know roughly when they logged in, know or brute-force the worker PID that minted the ID, and replay enough requests to find the surviving rand() seed. AC:L is indefensible — this is AC:H at best. Real-world impact is *session hijack of a specific user under heavy preconditions*, not unauthenticated RCE.
4 steps from start to impact.
Identify a target running vulnerable HTTP::Session
HTTP::Session::ID::SHA1. CPAN downloads for HTTP::Session are low-volume (sub-1k/month) so the addressable population is small.- Public-facing Perl web app
- Application uses HTTP::Session (not HTTP::Session2, Plack::Session, Catalyst::Plugin::Session, Dancer Session, or Mojolicious::Sessions)
- HTTP::Session is a niche, low-uptake CPAN module — most Perl shops use Plack::Session or framework-native sessions
- Cookie name is app-defined; no reliable banner
Harvest server-side state to narrow the seed space
Date: header to anchor epoch time within a few seconds, requests static endpoints repeatedly to estimate worker PID ranges (via fork patterns under prefork MPMs), and may chain a separate info-leak to read /proc/self/status or error pages that disclose PID. Each of these narrows the SHA-1 preimage search space.- Server returns Date header (default)
- Stable PID range observable, or PID leak available
- Behind a CDN or reverse proxy that strips/normalizes Date and rewrites error pages, PID is invisible
- Modern PID randomization (
kernel.randomize_va_space, largepid_max) inflates the search space
Brute-force session ID space against the target's session window
rand() has only the default time-based seed, the attacker enumerates SHA-1(rand_state ‖ epoch ‖ pid) and replays each as a session cookie until one authenticates. The work factor is ~2^30–2^40 hashes per *targeted minute*, dwarfed if rand() is reseeded or if the app calls srand with additional entropy.- Target user is actively logged in during the guessing window
- Server does not bind sessions to IP or User-Agent
- No anti-automation on the protected endpoint
- Even modest request-rate limiting per source IP makes online brute force impractical
- If the app calls
srandpost-fork with extra entropy (common in Plack workers), the keyspace explodes - Bot-management / WAF anomaly rules trigger on millions of failed cookie replays
Session hijack — single user impact
- Compromised session belongs to a high-value user for the impact to matter
- Step-up auth / MFA reprompt on sensitive actions blunts post-hijack damage
- Short session TTLs limit the value of any single guess
The supporting signals.
| In-the-wild exploitation | None observed. No public exploitation reports, no honeypot hits, no incident-response writeups referencing this CVE. |
|---|---|
| Public PoC | No weaponized PoC on GitHub or Exploit-DB. The oss-security thread for the sibling CVE-2026-3255 (HTTP::Session2) describes the methodology but stops short of a turnkey exploit. |
| EPSS | 0.53% (0.0053) — below the 50th percentile. EPSS agrees this is not a near-term exploitation target. |
| KEV status | Not listed by CISA, and unlikely to be added — no ransomware operator is going to brute-force SHA-1 preimages for niche Perl modules. |
| CVSS vector (vendor) | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8. AC:L is wrong — exploitation requires guessing rand() state + PID + epoch. Realistic vector is closer to AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N ≈ 5.9. |
| Affected versions | HTTP::Session < 0.54 on CPAN (Ktat distribution). Sibling defect in HTTP::Session2 < 1.12 is tracked as CVE-2026-3255. |
| Fixed versions | HTTP::Session 0.54+, switching default ID generator to Crypt::URandom / /dev/urandom. No major distro backports — this is a CPAN-only fix. |
| Exposure data | No Shodan/Censys fingerprint exists for HTTP::Session specifically — it's identifiable only by cookie shape. Monthly CPAN download counts are in the low hundreds; estimated installed base is small four digits globally. |
| Disclosure | Reported via oss-security on 2026-02-27 alongside CVE-2026-3255 (HTTP::Session2) and adjacent Mojolicious/Concierge session-ID CVEs; this CVE assigned 2026-03-28. |
| Reporter | Disclosed by the MetaCPAN Security WG / oss-security contributors auditing the CPAN session-module ecosystem; same class of finding as CVE-2026-2439 (Concierge::Sessions) and CVE-2026-9692 (Mojolicious::Sessions::Storable). |
noisgate verdict.
Downgraded from CRITICAL 9.8 to MEDIUM 5.3 because the *single most decisive factor* is attacker work factor: exploitation requires brute-forcing rand() state, PID, and epoch timing during a live session window, which is incompatible with the vendor's AC:L assumption. Blast radius is also bounded to a single user's session — there is no role-multiplier path to fleet, identity, or supply-chain compromise from the bug itself.
Why this verdict
- Work factor crushes AC:L. Vendor scored AC:L, but the real chain requires aligning
rand()state, PID, and epoch within a few seconds for a *targeted* user — that is textbook AC:H, dropping the score by ~3 points alone. - Impact is single-session, not system-wide. A successful hijack rides one user's privileges; there is no integrity write outside that user's app context and no availability impact. I:H/A:H in the vendor vector is not defensible.
- Installed base is tiny.
HTTP::Sessionis a niche CPAN module — most Perl web apps usePlack::Session,Catalyst::Plugin::Session,Dancer::Session, orMojolicious::Sessions. Estimated global installs are low four digits. - No exploitation pressure. EPSS 0.53%, not KEV, no PoC weaponization, no campaign signal. The bar to even *attempt* this against a real target dwarfs the bar to phish credentials.
- Role multiplier: low-value role (app session store). Even on a high-value app (banking portal, admin console), the chain delivers *one* user's session. The affected component is not in the identity/PKI/hypervisor/backup/CA/kernel-agent catalog — no fleet, domain, or supply-chain pivot is provided by the bug itself.
- Role multiplier: typical role (line-of-business Perl web app). Same — impact terminates at one hijacked session per successful brute-force window. Defense-in-depth (MFA, IP binding, short TTLs) further blunts the outcome.
- Role multiplier: high-value role. N/A —
HTTP::Sessionis not deployed as identity-plane infrastructure. There is no canonical high-value-role deployment of this component, so the deployment-role floor does not lift the verdict above HIGH.
Why not higher?
Not HIGH because exploitation requires sustained, noisy brute-force aligned with a live victim session — detectable by trivial rate-limiting or SIEM auth-failure alerting — and the addressable installed base is small four-digit globally. There is no role-multiplier path: the bug does not live in identity, PKI, hypervisor, backup, or kernel-agent code.
Why not lower?
Not LOW because the defect is real and exploitable in principle: an attacker with a separate PID/timing info-leak and a high-value target sitting behind a misconfigured proxy can succeed, and session hijack of a privileged user (admin, finance, healthcare clinician) is a non-trivial breach event. Upgrade to 0.54 is cheap, so leaving it on the backlog indefinitely is wrong.
What to do — in priority order.
- Force
HTTP::Session::ID::Generatorto a CSPRNG-backed implementation — Override the default by passingid_generator => HTTP::Session::ID::SHA1->new(rand_bits => sub { Crypt::URandom::urandom(32) })or swap in a custom generator that reads/dev/urandomdirectly. This neutralizes the predictable-seed defect without waiting for the package upgrade. No mitigation SLA at MEDIUM — but if a high-value app is affected, ship this within 30 days. - Bind sessions to client IP and User-Agent fingerprint — Even with a guessed cookie value, the attacker also has to spoof the originating client fingerprint. Implement in the PSGI middleware layer; rejects mismatched sessions before the handler runs.
- Cut session TTL and rotate cookie on privilege change — Short TTL (≤30 min idle, ≤8h absolute) shrinks the brute-force window. Rotating session IDs after login and after any role escalation invalidates pre-hijack guessing work.
- Rate-limit cookie replay on auth-bearing endpoints — Any single source IP submitting hundreds of distinct session cookies per minute is unambiguously hostile — block at the reverse proxy (nginx
limit_req, HAProxy stick-tables) or the WAF. - Strip the
Date:header at the edge and suppress PID leaks — Cuts off two of the three seed inputs an attacker needs. Configure your reverse proxy to overwriteDate:and ensure error pages don't leak$$or/procpaths.
SecureandHttpOnlycookie flags — they prevent XSS theft, but this bug lets an attacker *predict* the cookie without ever touching the browser.- WAF signature rules — there is no payload pattern; brute-forced cookies look exactly like legitimate ones.
- TLS / HSTS — the cookie value is predictable from server-side state, not intercepted on the wire.
- MFA on login — once a session cookie is hijacked, the attacker is *past* the login MFA prompt. Only step-up MFA on sensitive actions helps.
Crowdsourced verification payload.
Run on each Perl application host as the user that owns the app's lib path (or root). Invoke as ./check-cve-2026-3256.sh /path/to/app/lib — the script walks installed HTTP::Session modules and checks their $VERSION. Requires read access to the Perl @INC directories.
#!/usr/bin/env bash
# noisgate verification — CVE-2026-3256 (HTTP::Session < 0.54 weak session IDs)
# Usage: ./check-cve-2026-3256.sh [extra_lib_path ...]
set -u
FIXED_VERSION="0.54"
status="UNKNOWN"
found_any=0
if ! command -v perl >/dev/null 2>&1; then
echo "UNKNOWN: perl not installed on this host"
exit 2
fi
# Build -I args for any extra lib paths supplied (e.g. local::lib, Carton, app vendor dirs)
INC_ARGS=()
for p in "$@"; do
[ -d "$p" ] && INC_ARGS+=("-I$p")
done
result=$(perl "${INC_ARGS[@]}" -e '
use strict; use warnings;
my $fixed = "0.54";
my $worst = "PATCHED";
eval { require HTTP::Session };
if ($@) { print "NOT_INSTALLED\n"; exit 0; }
my $v = $HTTP::Session::VERSION // "unknown";
print "FOUND HTTP::Session $v\n";
if ($v eq "unknown") { print "STATUS UNKNOWN\n"; exit 0; }
# numeric compare on dotted/decimal CPAN versions
require version;
my $have = version->parse($v);
my $need = version->parse($fixed);
if ($have < $need) { print "STATUS VULNERABLE\n"; }
else { print "STATUS PATCHED\n"; }
' 2>&1)
echo "$result"
if echo "$result" | grep -q "NOT_INSTALLED"; then
echo "PATCHED: HTTP::Session is not installed in @INC"
exit 0
fi
if echo "$result" | grep -q "STATUS VULNERABLE"; then
echo "VULNERABLE: upgrade HTTP::Session to >= ${FIXED_VERSION} (cpanm HTTP::Session~'>=0.54')"
exit 1
fi
if echo "$result" | grep -q "STATUS PATCHED"; then
echo "PATCHED: HTTP::Session is at or above ${FIXED_VERSION}"
exit 0
fi
echo "UNKNOWN: could not determine HTTP::Session version"
exit 2
If you remember one thing.
HTTP::Session, deploy the CSPRNG generator override and IP/UA session binding within 30 days as defense-in-depth. For full remediation, upgrade CPAN dependencies to HTTP::Session >= 0.54 within the noisgate remediation SLA of 365 days, ideally rolled into your next quarterly dependency refresh (cpanm HTTP::Session~'>=0.54' or pin in cpanfile). Don't lose sleep over this one — but don't pretend the vendor's 9.8 means it should jump the queue ahead of your actual unauthenticated-RCE backlog. Run the verification script across every Perl host, file results in your SBOM, and move on.Sources
- oss-security disclosure thread (sibling CVE-2026-3255, same defect class)
- SentinelOne vulnerability entry — CVE-2026-3256
- OffSeq threat radar — CVE-2026-3256
- Tenable CVE record
- CWE-338 — Use of Cryptographically Weak PRNG
- HTTP::Session on MetaCPAN
- Crypt::URandom — the right way to do this on Perl
- PerlMonks — session id generation guidance
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.