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
HTTP::Session::ID::Generator to a CSPRNG-backed implementation — Override the default by passing id_generator => HTTP::Session::ID::SHA1->new(rand_bits => sub { Crypt::URandom::urandom(32) }) or swap in a custom generator that reads /dev/urandom directly. 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.limit_req, HAProxy stick-tables) or the WAF.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 overwrite Date: and ensure error pages don't leak $$ or /proc paths.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.
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). |
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
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.
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