Audit #40 Suspicious
Show full summary
Verdict: SUSPICIOUS. The legitimate Kirki Customizer Framework (Aristeides Stathopoulos, 2014–2023, 500,000 active installs, used by hundreds of WordPress themes as a dependency) was effectively replaced at v6.0.0 (released 2026-04-29) with a rebrand of Droip — an unrelated no-code page builder by Themeum, the company that received SVN commit access on 2023-05-18 via the literal commit "Contributor updated to Themeum".
The plugin header itself documents the substitution: the description changed from "The Ultimate WordPress Customizer Framework" to "an all-in-one no-code builder that empowers users to build professional-grade WordPress sites without writing any code," and the bootstrap code now explicitly guards if ( ! class_exists('KirkiProMain') && ! class_exists('KirkiMain') && ! class_exists('Droip') ) — confirming it ships as a downstream sibling of Themeum's Droip product. The MIT LICENSE file was deleted in the same release. Within days, the wp.org forum filled with reports from sites running themes like XStore, Lexend, Shoptimizer, Buddy X, and Gwangi — all broken with PHP fatal errors after auto-updating from kirki 5.x to 6.x.
The replacement code introduces a ComponentLibrary/controller/CompLibFormHandler.php REST surface with 6 routes (kirki-login, kirki-register, kirki-forgot-password, kirki-change-password, kirki-retrieve-username, kirki-comment) all wired to a permission_callback that unconditionally returns true. The session-bound nonce these routes verify is mintable by any anonymous visitor via wp_create_nonce in the unauth context — ElementGenerator::add_nonce_to_element() documents this with the comment "Always returns consistent nonce for same user + action for ~12 hours." Empirical testing on a controlled local site (poc-kirki.localhost) confirmed: unauth POST → auto-approved comment insertion succeeds (comment_approved=1, bypassing moderation); unauth registration with arbitrary user_meta injection succeeds (limited blast — meta is force-prefixed); the documented "phishing-vector password reset" + "arbitrary comment edit" primitives fail with PHP TypeErrors because the handler logic is structurally broken. Production error_reporting(E_ALL) + ini_set('display_errors', 1) calls in Ajax/Apps.php cause those PHP errors to render a Whoops stack trace with full filesystem paths back to anonymous callers (FPD-class info disclosure on every error). A download_zip_from_remote() helper at HelperFunctions.php:3801 explicitly disables SSL verification (verify_peer=false, verify_peer_name=false) for ZIP extraction into wp-content/, gated by a KIRKI_USERS_DEFAULT_FULL_ACCESS = array('administrator', 'editor') config that grants Editor-level users full plugin access.
This is not malware in the exfiltration-to-attacker sense — no third-party C2, no time-bombs, no obfuscated payloads. But the cumulative pattern — supply-chain takeover of a critical-mass framework, hostile replacement breaking the dependents, broken security primitives + structural defects in the replacement code, FPD via production error reporting, LICENSE file removed — warrants a public SUSPICIOUS verdict for site admins evaluating whether to keep this plugin installed.
Not yet confirmed malicious. Site owners should treat with caution; plugin author should review the cleanup steps.
If you run kirki on your site
Verify your install matches the wp.org canonical version:
wp plugin verify-checksums kirki
A patched build isn't yet published for this audit. Check the security advisories index or remove the plugin until one is available.
Plugins under the same committer's SVN access
themeum holds push access to 15 plugins totalling 664k+ active installs. Each non-target plugin scans clean today but represents a one-commit hijack opportunity.
Plugin version history
Every release on wp.org for this plugin, color-coded by relationship to the incident. The compromise window shows where the wp.org Plugin Review Team deleted the malicious tags from SVN — those versions cannot be re-downloaded today.
-
Clean 30 earlier releases before the incident
-
3.0.40 -
3.0.41 -
3.0.42 -
3.0.43 -
3.0.44 -
3.0.45 -
3.1.0 -
3.1.1 -
3.1.2 -
3.1.3 -
3.1.4 -
3.1.5 -
3.1.6 -
3.1.7 -
3.1.8 -
3.1.9 -
4.0.19 -
4.0.20 -
4.0.21 -
4.0.22 -
4.0.23 -
4.0.24 -
4.1 -
4.2.0 -
5.0.0 -
5.1.0 -
5.1.1 -
5.2.0 -
5.2.1 -
5.2.2
-
-
5.2.3Last clean Last clean release before incident -
6.0.0PRT cleanup PRT cleanup release — incident closed -
6.0.1Clean Clean (post-cleanup) -
6.0.2Clean Clean (post-cleanup) -
6.0.3Clean Clean (post-cleanup) -
6.0.4Clean Clean (post-cleanup) -
6.0.5Clean Clean (post-cleanup) -
6.0.6Clean Clean (post-cleanup) -
6.0.7Clean Clean (post-cleanup) -
6.0.8Clean Clean (post-cleanup) -
6.0.9Current Current release
Timeline
| Date | Event |
|---|---|
| 2014-05-27 | Kirki Customizer Framework added to wp.org by Aristeides Stathopoulos (aristath) |
| 2014–2023 | aristath is primary committer (154 commits). Plugin grows to ~500,000 active installs, used as Customizer-framework dependency by hundreds of WordPress themes |
| 2020-04-23 → 2023-02-08 | davidvongries (David von Gries) joins as secondary maintainer (32 commits) |
| 2023-05-18 | Ownership transition. Commit r2914504 by new committer themeum with message "Contributor updated to Themeum". Themeum becomes the sole active maintainer |
| 2023-06-13 → 2026-04-10 | Themeum-era maintenance: v4.2.0 → v5.2.3. Cadence consistent with traditional Customizer-framework patches |
| 2026-04-29 | v6.0.0 released — the rebrand. Plugin Name changes from "Kirki Customizer Framework" to "Kirki"; description changes to "all-in-one no-code builder"; 116 new files added, 7 deleted (incl. LICENSE), 51 modified |
| 2026-04-29 → 2026-05-08 | v6.0.1 → v6.0.5 — five rapid hotfix releases in 10 days as themes break across the install base |
| 2026-04-29 → 2026-04-30 | Forum complaints flood in: "Why have you ruined such a great plugin?", "XStore Sites Broken by Kirki 6.x Update", "Website broken after update to v6", "Site broken after update to v6" (12 replies), "Disable Builder" (10 replies), plus reports against Lexend, Shoptimizer, Buddy X, and Gwangi themes |
| 2026-05-15 | Current release v6.0.6 (audited here) |
Committer history (hijack-indicator data)
| Committer | First seen | Role | Commits |
|---|---|---|---|
plugin-master | 2014-05-27 | wp.org plugin-team bot | 1 |
aristath (Aristeides Stathopoulos) | 2014-05-27 | Original author | 154 |
deployer (deployer.seravo.com) | 2016-03-20 | Benign helper (resolved as benign_company_employee in events table) | 2 |
davidvongries (David von Gries) | 2020-04-23 | Second-era maintainer (transitional) | 32 |
themeum (Themeum) | 2023-05-18 | Current owner — initial commit literally titled "Contributor updated to Themeum" | 24+ |
Rebrand boundary diff (v5.2.3 → v6.0.0)
git diff --shortstat v5.2.3 v6.0.0
174 files changed, 35578 insertions(+), 1189 deletions(-)File-status breakdown: 116 added, 7 deleted, 51 modified.
Plugin header — the smoking gun
- Plugin Name: Kirki Customizer Framework
- Plugin URI: https://www.themeum.com
- Description: The Ultimate WordPress Customizer Framework
- Author: Themeum
- Copyright (c) 2023, Themeum
- License: https://opensource.org/licenses/MIT
+ Plugin Name: Kirki
+ Plugin URI: https://kirki.com
+ Description: Kirki is an all-in-one no-code builder that empowers users to build
+ professional-grade WordPress sites without writing any code.
+ Author: Kirki
+ Author URI: https://kirki.comBootstrap guard explicitly references Droip
// In kirki.php after the v6.0.0 rebrand:
if ( ! class_exists( 'KirkiProMain' ) && ! class_exists( 'KirkiMain' ) && ! class_exists( 'Droip' ) ) {
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/customizer/class-customizer.php';
}The Droip class check confirms the new code is the Droip page builder rebranded under the kirki slug.
Notable deletions in v6.0.0
| File | Significance |
|---|---|
LICENSE | MIT license file removed (no replacement) |
assets/images/kirki-logo.jpg | Original framework branding |
customizer/controls.js, customizer/preview.js | Core Customizer integration scripts that themes depend on |
customizer/packages/controls/tabs/edd/EDD_SL_Plugin_Updater.php | Legacy EDD updater (this was the source of the prior code_pattern event that resolved as fp_edd_updater_library) |
customizer/lib/upgrade-notifications.php | Legacy upgrade-notification module |
Notable additions in v6.0.0 (the Droip codebase)
| File | What it adds |
|---|---|
ComponentLibrary/controller/CompLibFormHandler.php | 6 unauth REST routes for login / register / forgot-password / change-password / retrieve-username / comment, all with permission_callback returning true |
ComponentLibrary/controller/ElementGenerator.php | Frontend element rendering — add_nonce_to_element() mints unauth nonces for the REST surface |
ComponentLibrary/controller/ShowUserMetadata.php | New user-meta display surface |
ComponentLibrary/index.php | define('KIRKI_COMPONENT_LIBRARY_APP_PREFIX', 'KirkiComponentLibrary') |
config.php | Top-level config: KIRKI_USERS_DEFAULT_FULL_ACCESS = array('administrator', 'editor') |
assets/fonts/Inter-*.woff (4 weights) | Droip's UI font family |
assets/js/kirki-editor.min.js, assets/js/kirki.min.js | Droip's editor + frontend JS |
Forgejo comparison: v5.2.3 vs v6.0.0
Hijack-indicator matrix
| Indicator | Result |
|---|---|
| Sole committer for ≥2 years? | No — three-stage ownership progression (aristath 2014–2023 → davidvongries 2020–2023 → themeum 2023→) |
| Sudden new committer event? | Yes — themeum's first commit on 2023-05-18 is literally titled "Contributor updated to Themeum", signaling a deliberate ownership transition |
| Author profile / plugin-purpose drift? | Yes — author transitioned from individual contributor → corporate vendor; plugin purpose entirely changed at v6.0.0 (Customizer Framework → no-code page builder) |
| Code-level malware patterns? | Structural defects, not active backdoors — broken unauth REST exploits (PHP TypeErrors halt the chain), production error_reporting causing Whoops/FPD, FULL_ACCESS gate grants Editor full plugin access by default, download_zip_from_remote() with verify_peer=false. No confirmed exfiltration / C2 / time-bomb |
| Outbound C2 / known bad domains? | Telemetry to vendor-controlled kirki.com only (no third-party C2). LICENSE file removed in 6.0.0 (compliance signal) |
| New SVN credentials before mass-impact release? | The themeum credentials were added 2023-05-18; the rebrand release shipped 3 years later (2026-04-29). Not a fresh-credential takeover — Themeum had legitimate maintainership and used it to ship the rebrand |
| Cross-cutting ecosystem damage? | Yes — forum reports confirm at least 5 major themes (XStore, Lexend, Shoptimizer, Buddy X, Gwangi) broke with PHP fatal errors when sites auto-updated from kirki 5.x to 6.x |
Empirically verified security findings on v6.0.6
| WP Registry finding | Audit-rated severity | Empirical reality |
|---|---|---|
| 45195 — "Unauthenticated arbitrary email with valid password-reset link (phishing vector)" | high | Broken in v6.0.6. Handler reaches get_password_reset_key() but nl2br(array) TypeError halts before wp_mail fires. User-enum + reset-key DoS work as side effects. Severity downgraded to low |
| 45196 (INSERT path) — "Unauthenticated auto-approved comment insertion bypassing moderation" | medium | Confirmed working. Unauth POST to /wp-json/KirkiComponentLibrary/v1/kirki-comment with anon nonce inserts wp_comments row with comment_approved=1. CVSS ~5.3 (below Patchstack 6.5 floor). sanitize_text_field() prevents XSS lift |
| 45196 (EDIT path) — "edits arbitrary comment IDs" | medium | Broken in v6.0.6. Handler line 126 references undefined $comment_id (should be $existing_comment_id). Whoops TypeError before wpdb->update fires |
| 45197 — "REST routes use return-true permission_callback for builder collection rendering" | low | Confirmed. get_item_permissions_check() returns true for all 6 REST routes (login/register/comment/forgot/change/retrieve). Structural defect — downstream impact depends on individual handler completeness |
| 45198 — "Unauthenticated registration creates user-meta from attacker-named keys" | low | Works but limited blast radius. All injected meta gets force-prefixed with KirkiComponentLibrary_. Attempted privesc via wp_capabilities injection results in KirkiComponentLibrary_wp_capabilities meta key only — actual role stays subscriber. No privesc |
Audit-749 (v6.0.4) additionally surfaced 14 findings (2C, 7H, 3M, 2L) including download_zip_from_remote() SSRF→RCE chain, SQL injection via bx_get_media, object injection via maybe_unserialize on attacker-supplied template data. The vulnerable code paths still exist in v6.0.6 source but are gated by FULL_ACCESS — which by default config grants the capability to administrators and editors only.
Comparable cases
- scroll-top (Beacon audit ID 12) — Plugin Update Checker library hijack by
Benjaminactor; classic new-committer-replaces-update-channel pattern. Kirki differs in that the takeover happened years earlier (2023-05) and the malicious change is a product-substitution rather than an update-channel hijack, but the supply-chain shape is the same. - siteguarding (Beacon audit ID 28) — multi-plugin portfolio takeover with backdoor. Kirki is single-plugin but the install-base impact (500K) and the dependent-theme blast radius are comparable.
- greenshift (Beacon audit ID 29, benign closed_by_wporg) — closure-explainer for guideline violations. Kirki could similarly land at benign-guideline if the takeover were just a rebrand without security defects; the cumulative pattern here lifts to suspicious.
What would lift the verdict to MALICIOUS
- Confirmed working exploit chain on a Customer/Subscriber/Unauth actor (the bugs as documented are structurally broken in v6.0.6 due to PHP TypeErrors)
- Outbound HTTP request to non-vendor-controlled infrastructure with exfiltrated data
- Hidden persistence (cron-jobs, options-table backdoors) — none found in source review
- Hardcoded credentials or magic-header authentication backdoors — none found
The codebase is incomplete and bug-ridden rather than actively malicious.