← All audits

Audit #50 Benign

Opal Woo Custom Product Variation · 400 installs · baseline 1.1.3 → head 1.3.6 · closed 2d ago

Actor: WPOPAL — commercial WooCommerce/Gutenberg plugin vendor (wpopal.com)
Show full summary

Verdict: BENIGN. Opal Woo Custom Product Variation is a healthy, actively maintained commercial plugin from WPOPAL (wpopal.com). It was flagged by WP Beacon's puc_update_hijack code-pattern rule, which fires on the Plugin Update Checker factory call — the same mechanism behind real update-channel takeovers like the 2021 anadnet campaign. On inspection, the flagged code is inert: it never executes in the wordpress.org build.

The updater call in the main plugin file is gated behind file_exists( … 'plugin-updates/plugin-update-checker.php' ). That library is not shipped in the wordpress.org version — and, critically, it has never been present in any tagged release (verified across the full git history, v1.1.3v1.3.4). Because the required file is absent, the require never runs, Puc_v4_Factory never loads, and buildUpdateChecker() is never called. This is standard vendor scaffolding: WPOPAL bundles the update-checker only in the paid/direct build it distributes from its own site, and leaves an inert, fail-safe stub in the free wp.org copy. The URL it would contact (source.wpopal.com) is the vendor's own domain, not third-party infrastructure.

This is the opposite of the anadnet pattern, where an attacker seeds installs with the library present and then removes it from trunk to hide persistence. Here the library was never in trunk at all. The plugin's file-upload feature (custom product fields → cart) uses WordPress's safe upload path (wp_handle_upload, wp_check_filetype_and_ext) with an admin-configured extension allowlist, so it does not permit executable (.php) uploads. Committer history is a single vendor identity (wpopal, plus the vendor's gutengeek brand account) with a normal release cadence and no hijack indicators.

The plugin is not closed and requires no action from site owners. Two minor code-hygiene notes (not security issues): the dead updater stub uses http:// rather than https://, and a leftover var_dump() debug statement remains in the upload validator.

Investigated — no compromise found.

Audit retained for the record. No action required.

Plugins under the same committer's SVN access

wpopal holds push access to 12 plugins totalling 3k+ active installs. Each non-target plugin scans clean today but represents a one-commit hijack opportunity.

GG Woo Feed for WooCommerce Shopping Feed on Google and Other Channels — clean code, same SVN account (latent risk)
1k+
Opal Service — clean code, same SVN account (latent risk)
900
Opal Mega Menu — clean code, same SVN account (latent risk)
400
Opal Portfolio — clean code, same SVN account (latent risk)
100
Opal Estate Custom Fields — clean code, same SVN account (latent risk)
40
Opal Estate — closed by wp.org
Opal Hotel Room Booking — closed by wp.org
Opal Membership — closed by wp.org
Opal Widgets For Elementor — closed by wp.org
Wpopal Core Features — closed by wp.org

Plugin version history

Every release on wp.org for this plugin. This audit found no malicious code; the version history is shown for reference.

  1. 1.1.3 Audit baseline Last clean release before incident
  2. Closure 72 days · 2024-07-31 → 2024-10-11

    wp.org closed this plugin pending review. No malicious code was found in any release; the closure reflects a policy decision (commonly: guideline compliance, vendor commercial-content rules, or extended unmaintenance). Releases below remain installed on existing sites and are not a security exposure.

  3. 1.1.4 Released after reopen PRT cleanup release — incident closed
  4. 1.1.5 Released Clean (post-cleanup)
  5. 1.1.6 Released Clean (post-cleanup)
  6. 1.2.0 Released Clean (post-cleanup)
  7. 1.2.1 Released Clean (post-cleanup)
  8. 1.2.3 Released Clean (post-cleanup)
  9. 1.2.4 Released Clean (post-cleanup)
  10. 1.3.0 Released Clean (post-cleanup)
  11. 1.3.1 Released Clean (post-cleanup)
  12. 1.3.2 Released Clean (post-cleanup)
  13. 1.3.4 Latest release Current release

Trigger

WP Beacon's CodeScanner fired puc_update_hijack (builtin, low) on version 1.3.5:

file: opal-woo-custom-product-variation.php  line 65
snippet: Puc_v4_Factory::buildUpdateChecker(
url_host: source.wpopal.com

The flagged code (main plugin file)

function owcpv_start() {
    if (file_exists(OWCPV_PLUGIN_DIR.'plugin-updates/plugin-update-checker.php')) {
        require 'plugin-updates/plugin-update-checker.php';
        if (class_exists('Puc_v4_Factory')) {
            Puc_v4_Factory::buildUpdateChecker(
                'http://source.wpopal.com/plugins/opal/opal-woo-custom-product-variation.json',
                __FILE__,
                'opal-woo-custom-product-variation'
            );
        }
    }
    return OWCPV_Start_Instance::instance();
}

Why it is inert

1. Library absent from the tree. git ls-tree -r HEAD contains no plugin-updates/ directory and no plugin-update-checker.php. grep -rl "Puc_v4_Factory" matches only the guarded stub in the main file. 2. Never in trunk, ever. git log --all --full-history -- 'plugin-updates/*' 'plugin-update-checker.php' returns no commits across the entire mirrored history (v1.1.3v1.3.4). The file was never removed-to-hide (contrast the anadnet / scroll-top pattern, WP Beacon audit #12); it simply is not part of the wp.org distribution. 3. Fail-safe gate. The file_exists() guard means the require and the buildUpdateChecker() call cannot execute when the library is missing — which is always, in the wp.org build. 4. Vendor-owned endpoint. source.wpopal.com resolves to the plugin author's own domain (Author URI / Plugin URI both wpopal.com). Not third-party, not a known IOC.

File-upload feature review

The plugin's headline feature adds custom product fields including file upload. Handler: includes/classes/class-owcpv-form-handler.php.

  • Uses wp_handle_upload() (core, safe) — not raw move_uploaded_file() to a web path.
  • owcpv_validate_ext_file_with_config() validates the extension against an admin-configured allowlist; images cross-checked with getimagesize() for real MIME.
  • wp_check_filetype_and_ext() + rejection when WP cannot determine a safe type (if ((!$type || !$ext))).
  • WordPress core rejects .php/executable types by default → no upload-to-RCE.

Danger-primitive sweep (all PHP at HEAD)

No eval, base64_decode, gzinflate, str_rot13, create_function, assert, preg_replace(/e), dynamic call_user_func, or wp_remote_* → eval/include chains. Outbound host references across the entire codebase: only source.wpopal.com (dead stub) and wpopal.com (vendor site).

Committer history

author_slugcommitsfirst seennote
wpopal322024-05-29vendor primary
gutengeek92024-05-02WPOPAL's Gutenberg-blocks brand
plugin-master12024-04-27wp.org review team (initial import)

Normal release cadence (1.1.x → 1.3.x, 2024–2026), single vendor identity, no sudden new committer.

Hijack-indicator matrix

IndicatorResult
Sole/consistent committer identity✅ Yes — WPOPAL (wpopal + gutengeek brand)
Sudden new committer near an event❌ No
Author profile drift❌ No
Code-level malware / obfuscation❌ No — updater stub is inert; no exec primitives
Outbound C2 / known-bad domains❌ No — vendor's own domain only, and unreachable code
Update-checker library removed to hide persistence❌ No — never present in trunk

Clean matrix → BENIGN.

Hygiene notes (non-security)

  • Dead updater stub uses http://source.wpopal.com (no TLS). Harmless while inert; if the paid build reuses it, it should be https://.
  • Leftover var_dump($ext) at class-owcpv-form-handler.php:551.

Comparable cases

  • WP Beacon audit #12 (scroll-top, MALICIOUS): genuine PUC hijack — library present and active, pointing at attacker infrastructure (cdnstaticsync.com), seeded then hidden. The structural inverse of this plugin.
  • Vendor self-updater FP class (wpcodefactory / algoritmika cross-selling): commercial vendors leaving pro-distribution scaffolding in the free wp.org build. Guideline-adjacent at most, not malware.