e107 v2.3.8
Caution
v2.3.8 is a security release for sites on v2.3.7 or earlier.
Upgrade from any 2.x at or below v2.3.7. If your site tracks the master branch, you are already past v2.3.8, so installing it would be a downgrade. v2.4.x is planned to be the next forward step.
Important
Upgrade promptly. v2.3.8 closes an unauthenticated SQL injection reachable on a default site, finishes the core-wide SQL-injection cleanup begun in v2.3.7, and removes an open redirect.
- Unauthenticated SQL injection in the news item view (GHSA-fr8h-3vfv-9q98, CVSS 8.6). A crafted news address let request data reach a database query without being made safe, with no login and no user interaction, on an ordinary front-end page. Any public-facing site is exposed. (d11929a0ff)
- All audited SQL-injection sinks closed across the core. v2.3.7 hardened many SQL injection risks, whether exploitable or not. v2.3.8 works through more risks that a follow-up audit found across admin, user, installer, and additional front-end code. There is no specific vulnerability mitigation but to upgrade.
- Code execution from a poisoned preferences value (GHSA-568x-w5qj-vr7c, CVE-2026-57859, CVSS 7.2). A stored user-preferences value was rebuilt with
eval(), so a hostile value written into the database out of band (for example through a separate injection) could run as PHP when that account's preferences were loaded. It is now parsed safely and rejects anything that is not plain stored data, which closes the second stage an injection would otherwise chain into. (59cf3aaaa0) - Open redirect in the download handler (GHSA-wcc5-8jrf-6q26, CVSS 6.1). A crafted download link bounced a visitor to any external site under the trust of your domain. (b1def30489)
You must upgrade to mitigate the numerous vulnerability risks.
Highlights
- [Security] Unauthenticated SQL injection through the news pages (GHSA-fr8h-3vfv-9q98). A single news item address carried a value straight into a database query without being made safe, so an attacker could run their own database commands with no login. It is now read as a number, as the surrounding queries already were. (d11929a0ff)
- [Security] SQL injection audit cleanup. v2.3.7 hardened many but not all SQL injection risks. A follow-up audit of the whole v2.3.x tree found more of the same pattern in admin, user-account, installer, and other front-end code. Those are now fixed too, so the same class of attack is closed off at all known areas of the e107 core code. Plugins should be audited separately and are not in the scope of fixes in this release. There is nothing for you to configure. (32940962e3, fb8fe63e9e)
- [Security] Stored preferences can no longer run as code (GHSA-568x-w5qj-vr7c, CVE-2026-57859). e107 was rebuilding a saved user-preferences value by running it through PHP's
eval(), with only a loose check on the front of the string. A hostile value planted in the database could therefore run as code the next time that account's preferences loaded. The value is now read with a strict parser that only accepts ordinary stored data and refuses anything else, so it can no longer be turned into code, even if something else managed to write into the database. (59cf3aaaa0) - [Security] Open redirect removed from the download handler (GHSA-wcc5-8jrf-6q26, CVSS 6.1). A download link could be crafted to bounce a visitor off to any other website while appearing to come from your domain, which is a common trick for phishing. That redirect is gone; a link that is not a real download now lands on the ordinary file-not-found page. (b1def30489)
- [Fix] Blank pages and emails when a theme has no override. Some templates (including the "notify" e-mail and the default page fallback) could come out empty because the template file was only read once but looked up under two names. The content is now captured reliably. (4b68ed8b40)
- [Fix] A leftover notification could take the whole site down. If a saved notification pointed at a plugin that had since been removed or renamed, every page that triggered an event (such as saving content) could go blank on PHP 7 and later. e107 now skips and logs the stale notification instead of stopping. (40e73cefde)
For Administrators
Changed
-
Deep audit SQL injection risks closed across the core (security). The following vulnerable areas are now closed off to SQL injection:
- the news item view (reachable with no login, GHSA-fr8h-3vfv-9q98),
- the admin batch-edit and search-filter machinery,
- user-account management,
- the plugin install/upgrade path,
- the installer's database-name and table-prefix handling,
- user-extended field definitions,
- emoticon pack names,
- poll previews,
- the display-name save, and
- the related-news block.
Nothing changes in how the site behaves and there is nothing to configure. Upgrading is critical to protect your site from potential exploitation. (d11929a0ff, 32940962e3, fb8fe63e9e)
-
The plugin/core update screen now carries a security token (hardening). The action to update your plugins/database could previously be triggered by a forged request if a logged-in main administrator was lured to a malicious page (a CSRF). It now requires e107's own one-time form token before any update runs, so only a genuine click on the real update button can start it. Sites that have deliberately turned off token protection are unaffected. (23fa132aee)
-
A saved value that could run as code is now read safely (security). e107 stores some data (such as a user's preferences) as text and reads it back when needed. One of those readers was rebuilding the value by running it through PHP's code evaluator, so a value tampered with directly in the database could execute the next time it was loaded. That reader now uses a strict parser that only understands ordinary saved data and rejects anything else, so a poisoned value fails safely instead of running. There is nothing to configure. Ref GHSA-568x-w5qj-vr7c, CVE-2026-57859. (59cf3aaaa0)
Fixed
- Open redirect in the download handler (security).
download/request.phpwould redirect to any web address placed in the link's query string, letting a craftedrequest.php?https://…link bounce a visitor off to another site under the trust of your domain (a phishing aid). Real downloads are served by their numeric ID and are unaffected; an address that is not a registered download now falls through to the normal file-not-found page. Ref GHSA-wcc5-8jrf-6q26. (b1def30489) - Blank template output when a theme provides no override. With no theme override in play, a template file that e107 looks up under two internal names was only read the first time, so the second lookup found nothing and rendered an empty result for the rest of the page load. This showed up on the "notify" administrator e-mail and the default page fallback, and on any merged template request. The template content is now captured once per file and reused, so those outputs come through. (4b68ed8b40)
- Stale notification could blank the site (non-security). A saved notification pointing at a plugin class that had since been removed or renamed threw an uncatchable error on PHP 7 and later, so every page that fired an event (for example saving content) could go blank. e107 now checks the class exists, skips and logs the stale entry, and carries on. Fixes #5604. (40e73cefde)
- News grid crash on PHP 8. A News grid menu whose caption is stored as a per-language array with no entry for the active language could stop the page with a
TypeError, because the array was handed todefined(). The caption is now checked before that call, so the grid renders. Fixes #5649. (ddf6726c1a) - Editor page-break button. In the TinyMCE editor the page-break button inserted a marker e107's pagination never recognized, so it did nothing. It now inserts e107's own
[newpage]marker, so a page break splits the content as intended. Fixes #5792. (1ef5658a52) - Site disclaimer year. The site disclaimer showed a literal "YYYY" to anything that read it directly (for example the RSS feed copyright) instead of the current year. The year is now substituted where the value is defined, matching the shortcode. Fixes #5714. (9d1258e696)
- Admin Help panels on PHP 8. The Log, Users and Upload admin Help panels emitted "Undefined array key" warnings while parsing the page address. They no longer reach for a value that is not there. Fixes discussion #5762. (c1545b1665)
- Log noise without the optional social-login integration. On sites that do not have the optional social-login plugin installed, e107 wrote a "failed to open stream" warning to the PHP error log on every page. The include is now guarded, so the log stays quiet; behavior is unchanged where the plugin is present. Fixes #5683. (79ce2dfed1)
For Developers
Added
- Regression coverage for the SQL-injection fixes.
SqlInjectionFixesTestpins the news item integer cast, the admin batch field allowlist (reject and accept paths), the user-model ID cast, the user-extended partial-update and create validation, and the installer name checks. The probe controller is built viaevalso the unit loader's include-time class scan does not pull inadmin_ui.php. (181eeba90e) e107TemplateSourceTest. Pins the capture-once-per-physical-file behavior so a template backing two registry paths cannot regress to caching an empty array for the second one. (4b68ed8b40)DownloadOpenRedirectCest+seeNoRedirectToacceptance helper. Assertsrequest.phpdoes not emit an off-siteLocationredirect. (b1def30489)e_array::unserialize()parser coverage. Newe_arrayTestcases pin the sink and the livee_user_pref::load()path, avar_export()round-trip, an edge-form round-trip (NUL / INF / NAN / PHP_INT_MIN), and an injection-rejection case; validated against thevar_export()/eval()baseline on PHP 5.6 through 8.5. (59cf3aaaa0)- Backport regression coverage.
news_classTestpins the news-grid array-caption guard;e107TinyMceParserTestandwysiwygTestpin the[newpage]pagebreak separator. (ddf6726c1a, 1ef5658a52)
Changed
- SQL-injection remediation techniques (2.3.x). The e107 v2.3.x branch predates the future
e_dbprepared-statement and query builder API, so tainted values are escaped inside their SQL quotes withescape(), numeric /LIMIT/OFFSETpositions are(int)cast, and dynamic table/column identifiers are confined to a strict[A-Za-z0-9_]allowlist (escaping cannot protect an identifier). The news item action reuses the integer cast it already computed;admin_ui/model_classvalidate the column against the controller's declared fields before it reaches the SET/WHERE clause, both at dispatch and unconditionally inbatchUpdate();e_user_model::load()int-casts the ID and the unadmin path moves its permission gate ahead of the query. If you maintain a plugin that builds raw SQL from request input, mirror these practices! (32940962e3) _getFieldValue()safestrnow escapes. Thesafestrfield type was serialized into single quotes withoutescape(), unlike the siblingstrbranch; itsfilter()pre-processing encodes quotes but leaves the backslash intact, so a trailing backslash could escape its own closing quote. It now callsescape()to matchstr, and the stored bytes are unchanged for any value without a backslash.xmlClass::e107Export()validates each requested table against the live table list before it reaches theFROMidentifier position. (fb8fe63e9e)- CSRF check at the
e107Updatechoke point.e107Update::__construct()readskey($_POST['update'])/key($_POST['update_core'])and runs the upgrade routines; both callers (db.php'sdb_updatebranch ande107_update.php) are gated only bygetperms('0'). A valid session token is now required at the shared constructor choke point before any update runs, validated throughe107::getSession()->check(false)(fail-closed, honorse_SECURITY_LEVEL); the real form emitted byrenderForm()now includes thee-tokenhidden field. (23fa132aee) e_array::unserialize()no longer useseval(). It rebuilt thevar_export()array-storage format by prepending$data =and callingeval(), guarded only by asubstr(...,0,5) === 'array'check, so a stored value beginning witharraythat carried a PHP expression executed on load (second-order, reachable throughe_user_pref::load()from a poisoneduser_prefscolumn). It is replaced by atoken_get_all()parser that accepts only the literal grammarvar_export()emits (array structure with string / int / float / bool / null / nested-array leaves) and fails closed otherwise. The multi-token edge forms (NULvia'a' . "\0" . 'b',INF/-INF/NAN,PHP_INT_MIN) are read faithfully, and the existing$data =strip,stripslashesfixups and PDO=>fix are retained, so legacy and addslash'd values still round-trip; stored bytes are unchanged for well-formed data. Ref CVE-2026-57859 / GHSA-568x-w5qj-vr7c. (59cf3aaaa0)- Template source captured once per physical path.
e107::_getTemplate()caches each template under an override (/ext) and a non-override registry path; with no theme override both resolve to the same file, butinclude_once()only populated the template variable on the first inclusion, so the second flavor cachedarray()and every later lookup on it returned false. The file's definitions are now captured once under thetemplates/source/registry namespace and re-read for each registry path the file backs. Template PHP still executes exactly once per request and theme overrides keep their distinct content. This clears themail.php"notify" FIXME (2f9dae5, 2014) and un-deadens thepage.php"default" fallback (46b2518, 2016). (4b68ed8b40)
Fixed
e107_event::trigger()tolerates stale handlers. Anotify_prefsentry whose class no longer exists is now skipped with a loggedEVENT_01warning behind aclass_exists()guard, and the handlercatchis widened to also cover\Throwable, so an\Errorraised inside a handler on PHP 7+ is logged rather than left to fatal. The widening is a secondcatch(\Throwable)aftercatch(\Exception)so the code stays parseable and correct on PHP 5.6, where\Throwableis undefined and\Exceptionalready catches every throwable. (40e73cefde)
Acknowledgements
Our thanks to Aastha Aggarwal (@Aastha2602) for responsibly disclosing the open redirect (GHSA-wcc5-8jrf-6q26), and to @Jimmi08, whose report prompted the wider SQL-injection audit behind this release (GHSA-fr8h-3vfv-9q98), both through GitHub Security Advisories. We also thank Saidakbarxon Maxsudxonov (@sermikr0) for reporting the preferences code-execution issue (GHSA-568x-w5qj-vr7c, CVE-2026-57859), coordinated through VulnCheck.