Introduction
A guest shopper's billing name should never be able to run script in the browser of whoever later views that order's documents, yet that is the exposure documented in CVE-2026-107750 for the PDF Invoices & Packing Slips for WooCommerce plugin. This one is a bypass of an earlier fix. The sanitization added for CVE-2026-92244 in version 5.16.2 worked as designed, and a decode step later in the rendering pipeline undid it.
PDF Invoices & Packing Slips for WooCommerce generates invoice and packing slip documents from WooCommerce order data. It is developed by WP Overnight, a WordPress and WooCommerce plugin shop acquired by UpdraftPlus in January 2023. WordPress.org lists the plugin at 300,000+ active installations, and WPScan reports a similar figure of 300,000. Because the plugin sits on the path between customer-supplied checkout data and generated document output, flaws in how it handles that data affect a substantial number of WooCommerce stores.
Technical Information
At a glance
| Attribute | Value |
|---|---|
| Weakness | CWE-79, improper neutralization of input during web page generation |
| Affected versions | All versions up to and including 5.16.4 |
| Attacker authentication | None required |
| Entry points | Billing first name, last name, company, and customer note at guest checkout |
| CVSS 3.1 | 7.2 High, supplied by Wordfence as CNA |
| Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N |
| First patched version | 5.16.5 |
Two notes on scoring. First, 7.2 is the Wordfence CNA score. NIST has not supplied its own assessment. Second, the published vector says UI:N, while the advisory narrative states that the script executes when a user accesses an injected page. We preserve both statements here rather than substituting a different vector.
Root cause: sanitize first, decode later
Version 5.16.2 addressed CVE-2026-92244 by applying a wp_kses() allow list to customer-supplied billing fields. kses works by recognizing literal tags, so a raw <img onerror=...> payload gets stripped. The problem was what happened after the template was rendered. OrderDocument::get_html() ran a document-wide cleanup pass over the finished HTML:
// clean up special characters if ( apply_filters( 'wpo_wcpdf_convert_encoding', function_exists( 'htmlspecialchars_decode' ) ) ) { $html = htmlspecialchars_decode( wcpdf_convert_encoding( $html ), ENT_QUOTES ); }
Now consider an entity-encoded payload such as <img src=x onerror=...>. It contains no literal angle brackets, so kses has nothing to match and passes it through unmodified. The template prints it, and at that moment it is harmless text. Then the final htmlspecialchars_decode() converts < and > back into real < and >, and the payload becomes live markup in the generated HTML.
The vulnerable 5.16.3 source corroborates this. OrderDocument.php renders the document template and then executes htmlspecialchars_decode( wcpdf_convert_encoding( $html ), ENT_QUOTES ), and BulkDocument.php contains the same decoding operation when assembling bulk document HTML. The decode is gated on the wpo_wcpdf_convert_encoding filter, whose default is simply whether htmlspecialchars_decode exists, so the decode runs unless a site has explicitly disabled it.
The sanitizer was there. The failure was in processing order: the input was sanitized, and a transformation that ran after sanitization reversed the effect. OWASP's XSS Prevention Cheat Sheet warns that modifying content after it has been sanitized can invalidate the protection. The security boundary is the HTML ultimately delivered for interpretation, and an intermediate string passing a filter does not establish that the final HTML is safe.
Attack flow
Based on the advisory and NVD description, the documented sequence is:
- An unauthenticated attacker places an order through guest checkout and supplies entity-encoded markup in the billing first name, last name, company, or customer note field.
- The 5.16.2 allow list processes the value but never encounters the literal tag delimiters it is designed to block, so the value is stored as is.
- When a document for that order is rendered, the template inserts the value, and the trailing
htmlspecialchars_decode()pass reconstructs the dangerous markup. - The injected script executes when a user accesses the affected page.
Scope of impact and exploitation status
The advisory establishes arbitrary script execution in pages. It does not establish that every PDF reader executes the payload, enumerate every viewing route, or document server-side code execution, administrator takeover, or a data theft incident.
On exploitation, TheHackerWire reports that no public exploit code has been confirmed and that the CVE is not recorded in CISA's Known Exploited Vulnerabilities catalog. These are secondary reporting statements, and we could not obtain primary KEV confirmation. No named threat actor or campaign is identified. Wordfence credits the researcher churchill11 with the finding.
Patch Information
WP Overnight fixed this in PDF Invoices & Packing Slips for WooCommerce 5.16.5, released October 20, 2026. Wordfence lists 5.16.5 as the patched version, and the CVE record points to WordPress.org SVN changeset 3756047 as the fix. The changelog entry for 5.16.5 is short: "Fix: Harden document output and shortcode order access". That release bundles more than one fix, and this section covers only the document output change behind CVE-2026-107750. The latest release is 5.16.6, which fixes an unrelated email attachment bug.
Keep the decode, escape customer text so it survives
The patch keeps the decode pass, which the plugin still needs for normal special characters, and makes customer-controlled values survive it. A new helper in includes/Documents/OrderDocument.php doubles every ampersand in customer text, but only while a document is being rendered:
public function protect_from_document_decode( $value ) { if ( ! $this->protect_from_decode ) { return $value; } if ( is_string( $value ) ) { return str_replace( '&', '&', $value ); } if ( is_array( $value ) ) { return array_map( array( $this, 'protect_from_document_decode' ), $value ); } return $value; }
With this in place, <img...> becomes &lt;img...&gt; before it reaches the template. The single htmlspecialchars_decode() peels off one layer and leaves <img...>, which renders as inert text. Plain values like "Smith & Sons" come out of the decode exactly as they went in.
The second change is in get_html(), which now controls when that protection is switched on:
$decode_html = (bool) apply_filters( 'wpo_wcpdf_convert_encoding', function_exists( 'htmlspecialchars_decode' ) ); $this->protect_from_decode = $decode_html; try { $html = $this->render_template( ... ); } finally { $this->protect_from_decode = false; } ... if ( $decode_html && $args['decode_html'] ) { $html = htmlspecialchars_decode( wcpdf_convert_encoding( $html ), ENT_QUOTES ); }
Three details stand out:
- The protection is tied to the decode. It only turns on if the
wpo_wcpdf_convert_encodingfilter would actually run the decode. If a site has disabled decoding, values are not escaped an extra time for no reason. - The try/finally always resets the flag. It returns to
falseeven if template rendering throws an exception, so the helper never leaks escaping into code outside document rendering. - A new
decode_htmlargument (defaulttrue) lets callers skip the decode pass. The bulk document fix below depends on it.
Which outputs are covered
In includes/Documents/OrderDocumentMethods.php, the patch wraps every output that carries customer-controlled text:
// billing & shipping address getters - return $address; + return $this->protect_from_document_decode( $address ); // billing custom field + return $this->protect_from_document_decode( apply_filters( 'wpo_wcpdf_billing_custom_field', $custom_field, $this ) ); // document notes + <?php echo $this->protect_from_document_decode( wpo_wcpdf_sanitize_html_content( $content, 'notes' ) ); ?> // order item meta + $data['meta'] = $this->protect_from_document_decode( wpo_ips_display_item_meta( $item, ... ) ); // shipping notes (customer note) + return $this->protect_from_document_decode( apply_filters( 'wpo_wcpdf_shipping_notes', $shipping_notes, $this ) );
This covers the billing first name, last name, and company (through the formatted address), the customer note, and several neighbouring sinks that had the same flaw. Wrappers around wpo_wcpdf_billing_custom_field and wpo_wcpdf_shipping_notes are applied after third-party filters run, so anything a filter adds is protected as well.
Closing the bulk export gap
When invoices are exported in bulk, the plugin builds one HTML file from each order's get_html() output, and the bulk wrapper performs its own special character cleanup. If each per-order call also decoded, content would be decoded twice. One layer of & cannot survive two decodes, and the payload would return. includes/Documents/BulkDocument.php now tells each per-order render to skip its own decode:
- $html_content[ $key ] = $document->get_html( array( 'wrap_html_content' => false ) ); + $html_content[ $key ] = $document->get_html( array( 'wrap_html_content' => false, 'decode_html' => false ) );
The per-order render still escapes customer values in advance, because the flag follows the filter, not the argument. Each piece of content is escaped once and decoded once.
The takeaway
The 5.16.2 fix sanitized input where it entered. The 5.16.5 fix considers the whole output path and accounts for the transformation that runs after sanitization. For any template pipeline that applies a decode or normalization pass after values are inserted, escaping has to match the number of decode passes that will actually run on the output.
Affected Systems and Versions
- Vulnerable: PDF Invoices & Packing Slips for WooCommerce, all versions up to and including 5.16.4.
- Still vulnerable despite the earlier fix: 5.16.2, 5.16.3, and 5.16.4. These include the CVE-2026-92244 allow list but are bypassed by entity-encoded input.
- First fixed: 5.16.5 (changeset 3756047, October 20, 2026).
- Latest: 5.16.6, which fixes the separate CVE-2026-109217 (unauthenticated sensitive information exposure via the yearly reset invoice attachment cache), affecting versions through 5.16.5.
- Relevant configuration: the injection path is guest checkout, and the vulnerable decode runs when the
wpo_wcpdf_convert_encodingfilter is enabled, which is the default whenhtmlspecialchars_decodeis available.
| Release | Security significance |
|---|---|
| 5.16.2 | Fixed CVE-2026-92244 with a wp_kses() allow list, which this CVE bypasses |
| 5.16.5 | First patched version for CVE-2026-107750 |
| 5.16.6 | Fixes the unrelated CVE-2026-109217, which matters when choosing an upgrade target |
The two later advisories describe different issues and should not be conflated. Version 5.16.5 is the answer for this XSS, and 5.16.6 or newer covers both.
Vendor Security History
The CVE record lists the vendor as unknown, but NVD's affected product data names wpovernight, and WordPress.org lists WP Overnight among the plugin contributors. The plugin changelog records several prior security fixes:
| Release | Changelog date | Security change |
|---|---|---|
| 3.7.6 | January 9, 2024 | Potential SQL injection in Number Tools |
| 3.7.7 | January 29, 2024 | SQL injection in Advanced Numbers search |
| 3.8.1 | April 23, 2024 | XSS involving WooCommerce checkout address input |
| 4.0.0 | February 4, 2025 | Document access security issue |
| 5.16.2 | 2026 | Fix for CVE-2026-92244, stored XSS via billing name and company fields (through 5.16.1) |
| 5.16.5 | October 20, 2026 | Fix for CVE-2026-107750, this bypass |
The 2024 checkout address XSS and the 2026 billing field issues both involve customer-supplied data reaching document output. The record shows consistent patch response, and it also shows that a released fix can leave a related processing path exposed. We would not read more into it than that. The history does not by itself support a numerical rating of the vendor's development practices.
References
- NVD entry for CVE-2026-107750
- Wordfence advisory for CVE-2026-107750
- Changeset 3756047: OrderDocument.php diff
- Changeset 3756047: OrderDocumentMethods.php diff
- Changeset 3756047: BulkDocument.php diff
- Changeset 3756047: OrderDocument.php (Trac view)
- PDF Invoices & Packing Slips for WooCommerce on WordPress.org
- Vulnerable 5.16.3 source: BulkDocument.php line 143
- Vulnerable 5.16.3 source: OrderDocument.php line 1699
- Vulnerable 5.16.3 source: OrderDocumentMethods.php line 1217
- Vulnerable 5.16.3 source: OrderDocumentMethods.php line 99
- Vulnerable 5.16.3 source: wpo_ips_functions.php line 630
- Wordfence advisory for CVE-2026-92244 (versions through 5.16.1)
- Wordfence advisory for CVE-2026-109217 (versions through 5.16.5)
- WPScan plugin vulnerability listing
- TheHackerWire analysis of CVE-2026-107750
- CISA Known Exploited Vulnerabilities Catalog
- OWASP Cross Site Scripting Prevention Cheat Sheet
- TeamUpdraft: UpdraftPlus acquires WP Overnight
- About WP Overnight



