CVE-2026-62996 — smarty/smarty
Fix: smarty-php/smarty@3c9f77aCVE-2026-62996 is a Path Traversal vulnerability in smarty/smarty. A fix is available for smarty/smarty — see the affected versions and patch details below.
Smarty Security stream restriction bypass through stream: resource
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-62996.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Real-World Exposure
smarty/smartyReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.
Description
smarty/smarty version 5.8.0 can read local files through PHP stream wrappers even when Smarty Security is enabled and all streams are disabled with Security::$streams = null.
The bypass uses Smarty's built-in stream: resource type. A template such as:
{include file="stream:php://filter/read=convert.base64-encode/resource=/tmp/secret.tpl"}
is handled as Smarty resource type stream, so the security check that would normally reject the underlying php wrapper is not applied. StreamPlugin then opens the nested php://filter/... URI directly.
For comparison, the direct resource:
{include file="php://filter/read=convert.base64-encode/resource=/tmp/secret.tpl"}
is blocked with stream 'php' not allowed by security setting.
Affected package:
- Ecosystem: Packagist / Composer
- Package:
smarty/smarty - Confirmed affected version:
5.8.0 - Confirmed source reference from Composer lock:
78d259d3b971c59a0cd719c270cc5cbb740c36a7 - Current stable version on Packagist at review time:
v5.8.0 - Packagist usage at review time: 41,113,855 total downloads and 840,604 monthly downloads
Relevant code paths:
Smarty\Resource\BasePlugin::load(...)Smarty\Resource\StreamPlugin::getContent(...)Smarty\Security::isTrustedStream(...)
BasePlugin::load() maps the built-in resource name stream directly to StreamPlugin before the code path that checks PHP stream wrappers with stream_get_wrappers() and Security::isTrustedStream($type). StreamPlugin::getContent() later calls fopen($filepath, 'r+') on the nested URI when the resource name contains ://.
Preconditions:
An application must render templates that are not fully trusted while relying on Smarty Security to restrict local files and PHP stream wrappers. The PoC sets:
$smarty->enableSecurity();
$smarty->security_policy->streams = null;
Local reproduction:
The PoC creates a disposable template directory and a separate outside directory. It enables Smarty Security, disables all streams, and then compares three includes:
../outside/secret.tplto confirm the ordinary trusted-directory boundary is enforced.php://filter/...to confirm direct PHP streams are blocked bySecurity::$streams = null.stream:php://filter/...to show the built-instream:resource bypasses the same restriction and reads the outside file.
Run:
php -d display_errors=1 poc.php
Observed sanitized output:
package=smarty/smarty
installed_version=v5.8.0
template_dir=<tmp>/templates
outside_template=<tmp>/outside/secret.tpl
plain_dotdot_include=BLOCKED:Smarty\Exception:Smarty Security: not trusted file path '<tmp>/outside/secret.tpl'
direct_php_filter_include=BLOCKED:Smarty\Exception:stream 'php' not allowed by security setting
stream_php_filter_include=OK:U01BUlRZX1NUUkVBTV9XUkFQUEVSX1NFQ1VSSVRZX0VTQ0FQRQ==
expected_base64=U01BUlRZX1NUUkVBTV9XUkFQUEVSX1NFQ1VSSVRZX0VTQ0FQRQ==
The plain_dotdot_include line shows the directory boundary is enforced for ordinary traversal. The direct_php_filter_include line shows the same policy rejects php://filter when used directly. The stream_php_filter_include line shows that wrapping the same URI in Smarty's stream: resource bypasses that restriction and reads the outside file.
Impact:
A template author can bypass Smarty Security stream restrictions and read local files that are readable by the PHP process. With php://filter, file contents can be base64 encoded and rendered back through the template. This bypasses both the intended Security::$streams = null restriction and the normal trusted-template-directory check that blocks ../ traversal.
Duplicate checks:
- OSV query for
Packagist/smarty/smartyversion5.8.0returned no vulnerabilities. - GitHub advisory query for
ecosystem=composerandaffects=smarty/smartyreturned historical Smarty advisories, including sandbox escapes, PHP code injection, XSS, and older path traversal issues. The listed path traversal advisories affect older versions and do not describe this currentstream:php://filterresource-wrapper bypass in 5.8.0. - GitHub issue search in
smarty-php/smartyforstream:php://filterreturned zero results. - Public searches for
Smarty StreamPlugin php://filterandsmarty/smarty isTrustedStream stream:did not identify a clear public duplicate during triage.
Suggested remediation:
When resolving the built-in stream: resource, parse and validate the nested URI scheme before opening it. For example, stream:php://filter/... should call Security::isTrustedStream('php'), and Security::$streams = null should block the resource before StreamPlugin::getContent() reaches fopen().
It would also be safer for StreamPlugin to reject nested stream wrappers by default unless the underlying wrapper is explicitly allowed by the active security policy.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐘Packagist | smarty/smarty | ≥ 5.0.0&&< 5.8.4 | 5.8.4composer require smarty/smarty:^5.8.4 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for smarty/smarty, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update smarty/smarty to 5.8.4 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-62996 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-62996 can be triaged on real exposure rather than presence alone.
Tailored to CVE-2026-62996. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-62996 in your dependencies?
O3 Security finds CVE-2026-62996 across Packagist dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.