Skip to main content

Boteraser | Website and Server Security Solutions

🛡️ CVE-2026-45710 — facturascripts

🟢 CVSS 2.0 — Low ✅ No Known Exploit CWE-116 OSV
2.0
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

FacturaScripts: Stored XSS in WidgetVariante and WidgetSubcuenta modal lists via HTML-attribute decoding of Tools::noHtml-escaped quotes inside onclick=

Summary

WidgetVariante::renderVariantList (Core/Lib/Widget/WidgetVariante.php:298-330) and WidgetSubcuenta::renderSubaccountList (Core/Lib/Widget/WidgetSubcuenta.php:290-321) build the <tr onclick="..."> row for each modal hit by concatenating the user-controlled referencia / codsubcuenta field directly into a single-quoted JavaScript string literal inside an HTML onclick attribute. The defender's intuition is that Tools::noHtml (called in Variante::test() and Subcuenta::test()) replaces ' with the HTML entity ', neutralising the JavaScript string break. The intuition is wrong: HTML attribute parsing decodes character references before the JavaScript fragment is parsed, so ' becomes a literal ' in the JavaScript context. An attacker who can store a value such as 1',alert(1),'2 in Variante.referencia (no special characters required, just one apostrophe) ends up with widgetVarianteSelect('id', '1',alert(1),'2'); executing in any user's browser the moment they open the variant-picker modal.

The recent 40bc701 and 8586b97 fixes corrected the same anti-pattern in three sister classes by switching to data-reference="..." + this.dataset.reference. The two widget classes audited here were missed by that fix wave.

Details

the offending code

Core/Lib/Widget/WidgetVariante.php:298-330:

```php

protected function renderVariantList(): string

{

$items = [];

foreach ($this->variantes() as $item) {

$match = $item->{$this->match};

$description = Tools::textBreak($item->description(), 300);

...

$items[] = '<tr class="clickableRow" onclick="widgetVarianteSelect(\'' . $this->id . '\', \'' . $match . '\');">'

. '<td class="text-center">'

...

```

$this->match defaults to 'referencia' (WidgetVariante::__construct, line 42). $item->referencia was sanitised at write time by Variante::test() (Core/Model/Variante.php:392) which calls Tools::noHtml($this->referencia). Tools::noHtml (Core/Tools.php:499-504) replaces ', ", <, > with ', ", <, >. The defender therefore expects that any apostrophe a user typed becomes ' in the database, which renders inside the onclick attribute as ' and cannot break out of the surrounding '...' JS string literal.

Core/Lib/Widget/WidgetSubcuenta.php:290-305 has the identical shape:

```php

foreach ($this->subcuentas() as $item) {

$match = $item->{$this->match};

...

$items[] = '<tr class="clickableRow" onclick="widgetSubaccountSelect(\'' . $this->id . '\', \'' . $match . '\');">'

. '<td class="text-center">'

. '<a href="' . $item->url() . '" target="_blank" onclick="event.stopPropagation();">'

...

```

$this->match defaults to 'codsubcuenta'; the value is Tools::noHtml-encoded by Subcuenta::test() (Core/Model/Subcuenta.php:213).

why HTML-entity escaping does not protect a JavaScript string

Per the HTML5 spec (and what every browser actually does), the value of an HTML attribute is processed by the character reference state of the tokenizer before any consumer sees it. By the time the onclick attribute value reaches the script engine, the bytes inside are the *decoded* string. Concretely, the HTML the browser receives is:

```html

<tr onclick="widgetVarianteSelect('id', '1',alert(1),'2');">

```

After the tokenizer decodes ' to ', the JavaScript fragment passed to the script engine is:

```javascript

widgetVarianteSelect('id', '1',alert(1),'2');

```

alert(1) runs as a third positional argument that JavaScript happily evaluates while building the call. The widgetVarianteSelect function ends up being called with four arguments and the side-effect of alert(1) (or any payload) has already occurred.

The recent 40bc701 AccountingModalHTML and 8586b97 SalesModalHTML / PurchasesModalHTML fix recognised this. Both replaced the onclick="...('"+ value +"')" pattern with:

```php

$tbody .= '<tr ... data-subaccount="' . $code . '" onclick="$(...).modal(\'hide\');'

. ' return newLineAction(this.dataset.subaccount);">'

```

Where $code = static::html($subaccount->codsubcuenta) and static::html is htmlspecialchars(html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'). The HTML5 entity decode is deliberate: it normalises any double-encoded data so that the subsequent htmlspecialchars produces stable single-encoded output. The JavaScript then reads the value from this.dataset.*, which is the post-decoded attribute value, where the original quote is now literally inside a string property and cannot break out of any quote context.

WidgetSubcuenta and WidgetVariante were not migrated to this pattern.

ways to plant t

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs administrative privileges on the target. A user must be tricked into taking some action. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality low, integrity low, availability none.

CVSS metrics in full

The score comes from this vector: CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:L/A:N

  • Attack vector: Network — reachable from anywhere that can route to the service.
  • Attack complexity: Low — the attack works reliably, with no preparation.
  • Privileges required: High — administrative rights are needed first.
  • User interaction: Required — someone has to click, open or visit something.
  • Scope: Unchanged — the damage stays inside the vulnerable component.
  • Confidentiality impact: Low — limited, and the attacker does not choose what is affected.
  • Integrity impact: Low — limited, and the attacker does not choose what is affected.
  • Availability impact: None.

Weakness class

CVE-2026-45710 is classified as CWE-116: Improper Encoding or Escaping of Output. Output is emitted without encoding it for the context it lands in, so data is interpreted as markup or code.

Affected software

CVE-2026-45710 is recorded against 1 package.

  • facturascripts/facturascripts

Timeline and source

Published on 14 July 2026 and last revised on 28 July 2026. No public exploit is currently recorded for this entry. Record sourced from OSV.

References

github.com (Web)
github.com (Package)

Other advisories for this package

facturascripts/facturascripts has other advisories on record. If you are patching this one, these are worth checking on the same host:

Same weakness in other software

These advisories are the same class of weakness (CWE-116: Improper Encoding or Escaping of Output) in other software:

Details

Severity LOW
CVSS Score 2.0
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:L/I:L/A:N
CWE CWE-116
Public Exploit ✅ No
Source OSV
Published 2026-07-14
Updated 2026-08-20
Modified 2026-07-28
Fix URL N/A

Affected Packages

Software From version Fixed in
facturascripts/facturascripts

Similar Threats

Free Vulnerability Check

Is your site affected by CVE-2026-45710?

BotEraser helps you identify potentially vulnerable plugins and themes by checking your installation against CVE-2026-45710 and other known CVE records.

Scan My Site Free →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.

Browse related advisories

All advisoriesCVECVE 2026