WP Manifestindependent plugin directory
manifest / ecommerce / dpdconnect

DPD Connect for WooCommerce WP Registry grade D+. High-severity findings · 4 findingsWP RegistryHigh-severity findings · 4 findingsOpen the reportD+

WordPress plugin "DPD Connect for WooCommerce".

★ 2stars
32release downloads
≈10active sites
v1.4.5latest · 2 years ago

Install

Download dpdconnect.1.4.5.zip From the v1.4.5 release on GitHub, Jan 30, 2024

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/pronamic/dpdconnect/releases/download/v1.4.5/dpdconnect.1.4.5.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Releases

3 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
v1.4.5 latest
Jan 30, 2024 2 years ago
dpdconnect.1.4.5.zip
14
Jan 30, 2024 2 years ago
dpdconnect.1.4.4.zip
9
Jan 30, 2024 2 years ago
dpdconnect-1.3.6.zip
9

Active-site estimate ≈10 comes from the median of recent superseded releases. Method.

D+ grade

Security

DPD Connect for WooCommerce 1.4.5 · audited by WP Registry

High-severity findings.

2 high 2 medium
Audited release
1.4.5
Findings
4
Worst severity
high
Content hash
9e7705a00189f1a7220746c1…

Findings

  • high Unauthenticated shipping label PDF download via sequential IDs

    missing_auth

    On every front-end and wp-admin request, dpdconnect.php calls Router::init($_GET) at plugin-load time, before WordPress runs the admin capability screen. If $_GET['plugin'] is present and $_GET['file']==='shipping_label', the handler loads wp_dpdconnect_labels by $_GET['id'] and Download::pdf() sends the stored blob as an attachment then exit()s. There is no current_user_can, no nonce, and no login check. The intended plugin===dpdconnect guard is written as (!$parameters['plugin'] === 'dpdconnect'), which is always false due to ! vs === precedence, so any plugin query argument works. Label IDs are mediumint AUTO_INCREMENT, so an unauthenticated visitor enumerates /?plugin=dpdconnect&file=shipping_label&id=1,2,3… and obtains every generated shipping/return label (recipient name, street, postcode, phone, parcel number). Admin UI links (OrderColumn, DownloadLabelBox, JobList) advertise exactly this query string on admin_url() with no nonce. Observable consequence: mass PII/label theft; upstream woocommerce-shipping 2.x added manage_woocommerce plus a per-id wp_nonce_url and moved the router to init.

    classes/Router.php:11-26

    Recommendation

    Do not dispatch downloads at plugin-load. On init, require current_user_can('manage_woocommerce') and wp_verify_nonce bound to the label id (as upstream 2.x Router::labelUrl / downloadShippingLabel). Fix the plugin slug comparison to !==. Refuse missing/unknown ids without dumping notices.

  • high Unauthenticated DPD batch callback with no shared secret

    missing_auth

    Callback::handle registers only admin_post_nopriv_dpdbatch. listen() json_decodes php://input and, for state 4 (or 20), uses the caller's jobid/orderId/parcelNumbers to Connect\Label::get() (authenticated to DPD with the store's Connect username/password), INSERT a row into wp_dpdconnect_labels, update job/batch status, optionally wp_mail a tracking email, and add_meta_data dpd_tracking_numbers. For state >= 8 it writes attacker-supplied stateMessage/error onto the job (JobView pretty-prints state_message and unserializes error in an admin <pre> with no escaping). There is no HMAC, token, or origin check — createUrl() is just admin-post.php?action=dpdbatch. A valid DPD job UUID is not strictly required for the success path: getByExternalId on a miss still proceeds into try and will persist a label if getLabel() succeeds. Combined with finding 1, anything inserted is immediately downloadable. Observable consequence: unauthenticated DPD API abuse with store credentials, unauthorized label materialization, job-status tampering, and stored HTML/JS in the manage_options job UI. Upstream 2.x rejects callbacks unless $_GET['token'] hash_equals a dpdconnect_callback_secret.

    classes/Handlers/Callback.php:19-46

    Recommendation

    Append a high-entropy secret to the callback URL registered with DPD and reject requests that do not hash_equals it (upstream Callback::secret). Also verify the job exists before calling the DPD label API or writing status, escape state_message on output, and persist order meta only after $order->save() on a real WC_Order.

  • medium save_post generates DPD labels with no capability or nonce check

    missing_capability

    GenerateLabelBox::process is hooked to save_post and, whenever $_POST['generate_label'] is set and $_POST['label_type'] is non-empty, calls LabelRequest::single($postId, $_POST['label_type'], $_POST['parcel_count']) with no current_user_can and no wp_verify_nonce. LabelRequest::single books a shipment against the DPD Connect API and Download::pdf() exits the request with a PDF. On stores still using shop_order as a CPT, WooCommerce checkout is a POST that wp_insert_post()s the order in the same request, so a guest or customer can add those extra fields to the checkout POST and force a paid label for the order they just placed (parcel_count is unsanitized). HPOS-only shops generally do not fire save_post for orders, so that unauth path does not apply there; the missing gate remains on any other save_post that carries those keys. Observable consequence: unauthorized DPD shipment/label creation and billing on classic-order checkouts; the request is then replaced by a PDF download.

    classes/Handlers/GenerateLabelBox.php:12-14,73-80

    Recommendation

    Replace save_post with woocommerce_process_shop_order_meta / woocommerce_saved_order_items. Require current_user_can('manage_woocommerce') and a dedicated nonce from the metabox. Restrict $postId to shop_order / wc_get_order() and absint parcel_count. Do not run this logic during storefront checkout.

  • medium Stored checkout XSS via unsanitized DPD shipping description

    xss_stored

    woocommerce_after_shipping_rate echoes dpd_checkout_description from the shipping-method instance option inside an HTML <p> with no escaping. sanitize_dpd_checkout_description is the field's sanitize_callback but returns the submitted $value unchanged when it is non-empty (only empty values are replaced with DPD's descriptionCheckout). Shop Manager (manage_woocommerce, no unfiltered_html) can therefore persist <script> or event-handler markup that executes for every visitor on cart and checkout — a skimmer-class consumer. Observable consequence: arbitrary JavaScript on the payment page for anyone who can edit shipping zones (below manage_options).

    classes/Handlers/ShippingMethods.php:74-89,93-106

    Recommendation

    Escape the description with esc_html() (or wp_kses_post if limited markup is required) at echo time, and run sanitize_text_field() in sanitize_dpd_checkout_description before saving.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.