Start with the problem you are seeing
Could this match the problem on your WordPress site?
Use this report when an unfamiliar admin-helper.php file appears in wp-content, a boot-loader.php file appears under mu-plugins, or the database contains wp_helper_uid. The recovered pair can log a requester in as an existing administrator without a password.
Observed in this investigation
- Two PHP files used the same database option and nearly identical administrator-selection and cookie-creation logic.
- The database option stored user ID 10, whose captured metadata included administrator capability and user level 10.
Possible warning signs
- An attacker may enter wp-admin as an existing administrator without knowing that account's password.
- Deleting only one file may leave the second passwordless access path available.
- A missing installer or sibling-site implant may recreate the files unless the broader hosting account is investigated.
These signs are investigation leads, not proof of this artifact by themselves.
Questions this research can help answer
- Why are admin-helper.php and boot-loader.php in my WordPress installation?
- Can WordPress malware create an administrator login cookie without a password?
- How do I remove wp_helper_uid malware and revoke every forged session?
Evidence boundary: The file pair and database records prove passwordless administrator-session capability, but logs or a missing installer are required to identify initial access or confirmed operator use.
Seeing one of these signs on your site?
I can investigate the files, database records, users, and server evidence, explain what is confirmed, and help plan a safe cleanup.
Research focus: Static analysis of paired admin-helper.php and MU-plugin backdoors that mint persistent WordPress administrator sessions without passwords.
I recovered two small WordPress backdoors that do not steal or crack an administrator password. Instead, they ask WordPress itself to create a valid authenticated session for an existing administrator.
The first file, wp-content/admin-helper.php, is a directly reachable PHP endpoint. The second, wp-content/mu-plugins/boot-loader.php, is loaded automatically by WordPress. Both use the same database option, wp_helper_uid, and almost identical account-selection and cookie-generation logic. Each accepts a different hard-coded request secret, remains quiet when that secret is absent, and redirects a successful requester into wp-admin.
Assessment: Critical compromise. An operator who knows either embedded secret can obtain the selected administrator’s WordPress privileges without presenting that user’s password or completing the normal login flow.
The public evidence deliberately withholds both live request secrets. Publishing them would turn defensive research into an access recipe for any site where the samples remain installed. Their hashes, stable code markers, file hashes, option name, and normalized control flow are sufficient for identification.
Summary
admin-helper.phploads WordPress from a nearbywp-load.php, checks a secret request parameter, and displays a fake 404 response to ordinary visitors.boot-loader.phpis a must-use plugin. It checks a second secret-gated parameter before registering a priority-1plugins_loadedcallback.- Both read
wp_helper_uid, enumerate administrator accounts in ascending ID order, and prefer the first administrator whose ID differs from the stored value. - If no different administrator exists, both fall back to the stored helper ID.
- Both call
wp_set_current_user()andwp_set_auth_cookie()without calling the password-authentication flow. - Both request a persistent login and redirect to
wp-admin. - The database contained
wp_helper_uid = 10; captured usermeta showed that user ID 10 had the site’s administrator capability and user level 10. - Neither sample creates the option, creates the user, writes the other file, or reveals the original compromise route. A third component or an interactive attacker action remains missing.
Scope and methodology
This report is based on the two recovered PHP files, their exact byte-level hashes, two Plesk directory captures, and read-only phpMyAdmin results. I normalized split strings and misleading identifiers, removed dead assignments, and traced the WordPress functions and hook order. PHP syntax was checked with php -l; the samples were not executed as applications, WordPress was not loaded during analysis, and no request was made to either gated route.
Exact searches for the two file hashes, embedded secrets, and distinctive header comments produced no useful public matches on 30 August 2026. Search-engine absence is not proof that the cluster is unique or previously unknown.
The cluster is provisionally described here as the wp_helper_uid dual WordPress administrator-session backdoor. That is a functional label, not attribution to a person, company, or established malware family.
Recovered filesystem evidence
The first sample appeared directly beneath wp-content rather than inside a normal plugin directory.

Figure 1 — admin-helper.php is displayed at 2.4 KB with permission mode 0644. Its displayed modification time is 30 July 2026 at 12:21, but that value alone cannot establish when the compromise occurred.
The companion occupied the root of wp-content/mu-plugins.

Figure 2 — boot-loader.php is displayed at 2.1 KB, also with mode 0644 and the same displayed modification time. The shared timestamp is consistent with coordinated placement or timestamp copying, but is not a reliable infection timeline.
WordPress automatically enables PHP files placed directly in wp-content/mu-plugins, loads them before ordinary plugins, and does not offer the normal deactivation control for them. WordPress documents that removal from the MU-plugin directory is required to disable such a file. See the official must-use plugin documentation.
Confirmed findings
Two routes lead to the same administrator-session outcome
The samples differ in how the operator reaches them:
| Component | Loading path | Wrong or missing secret | Successful path |
|---|---|---|---|
wp-content/admin-helper.php | Direct HTTP request to the PHP file | Returns a fake, non-cacheable 404 page | Loads WordPress, mints the selected administrator session, redirects to wp-admin |
wp-content/mu-plugins/boot-loader.php | Automatically loaded during an ordinary WordPress request | Returns from the MU-plugin and lets WordPress continue | Runs during plugins_loaded, mints the selected administrator session, redirects to wp-admin |
The MU-plugin declines AJAX and REST contexts. This appears designed to reduce breakage and avoid revealing the redirect during API traffic. Its request parameter is named like a scheduler, but the file contains no scheduling logic; the name is camouflage.
The string splitting is camouflage, not encryption
Neither sample contains a compressed or encrypted second stage. The apparent complexity comes from:
- split strings assembled with
implode; - unrelated hexadecimal comments;
- overwritten variables;
- unused arrays and integers;
- random word-salad identifiers;
- meaningless
function_existsprobes; and - one impossible comparison in
admin-helper.php.
After removing this noise, the operational code is short and direct.
The backdoor selects an administrator strategically
Both samples follow the same selection algorithm:
- Read
wp_helper_uidas an integer. - Query users with the
administratorrole. - Sort by ascending user ID.
- Select the first administrator whose ID does not equal
wp_helper_uid. - If none exists, fall back to the stored ID.
- If no usable ID exists, stop or return 404.
This means the option is not required for the attack to work. If the option is absent and evaluates to zero, the query still chooses the first administrator. Deleting only wp_helper_uid does not neutralize either file.
The preference for a different administrator is important. One plausible interpretation is that the stored ID identifies a helper or planted account, while the operator prefers to impersonate a legitimate low-ID administrator. That motive remains an inference because the missing installer and user-creation history were not recovered.
WordPress creates a real persistent session
The decisive call in both samples is structurally equivalent to:
wp_set_current_user($target_user_id);
wp_set_auth_cookie($target_user_id, true, is_ssl());
do_action('wp_login', $target_user->user_login, $target_user);
No password validation occurs before these calls. The true argument requests a remembered session. WordPress documents a default 14-day duration, subject to the site’s auth_cookie_expiration filter. When no session token is supplied, WordPress creates one through its session-token manager and then generates the authentication and logged-in cookies. See wp_set_auth_cookie().
Calling do_action('wp_login', ...) can also invoke ordinary login-monitoring, auditing, or security-plugin callbacks. It makes the event look more like a normal WordPress login to code that watches the hook, even though the password-authentication process was bypassed.
Database evidence links the option to an administrator-capable account
The supplied read-only query returned exactly one wp_helper_uid option. Its captured value was 10, the value length was two bytes, and autoload was disabled.

Figure 3 — Both PHP samples read this exact option. autoload = off does not protect the site because each backdoor retrieves the option explicitly with get_option().
The corresponding metadata capture showed user ID 10 with the site-prefixed capabilities value a:1:{s:13:"administrator";b:1;} and user level 10.

Figure 4 — The database supports the narrow conclusion that the stored ID was administrator-capable. It does not establish whether the account was created by the attacker or was an existing legitimate administrator.
Query-only screenshots and empty results were excluded. In particular, a generic wp_capabilities lookup did not use the captured site’s actual capability-meta prefix, so its empty result cannot support a negative finding.
Technical analysis
admin-helper.php: direct endpoint with a fake 404
The file tries three relative paths until it finds wp-load.php. From the observed wp-content location, the final candidate normally resolves to the WordPress root. If WordPress cannot be loaded, the script returns 404.
It then reconstructs a hard-coded secret and compares it with request parameter s using hash_equals(). A missing or incorrect value receives a deliberately ordinary 404 document plus no-cache headers. This conceals the endpoint from casual browsing and scanners that do not know the secret.
Redacted defensive excerpt
$wordpress_candidates = [
__DIR__ . '/../../../wp-load.php',
__DIR__ . '/../../wp-load.php',
__DIR__ . '/../wp-load.php',
];
// Exact embedded value withheld from publication.
$request_secret = '[REDACTED]';
if (!isset($_GET['s']) || !hash_equals($request_secret, (string) $_GET['s'])) {
status_header(404);
nocache_headers();
echo '<!DOCTYPE html><title>404 Not Found</title><h1>Not Found</h1>';
exit;
}
The gate secret’s SHA-256 is published below for private-sample comparison without disclosing the usable value.
boot-loader.php: automatically loaded alternate gate
The MU-plugin reconstructs a different secret and checks a parameter named cron. Unless it matches, the file returns immediately. It also returns for AJAX or REST requests. Only a correctly gated ordinary request reaches the registered callback.
// Exact embedded value withheld from publication.
$request_secret = '[REDACTED]';
if (!isset($_GET['cron']) || !hash_equals($request_secret, (string) $_GET['cron'])) {
return;
}
if ((defined('DOING_AJAX') && DOING_AJAX)
|| (defined('REST_REQUEST') && REST_REQUEST)) {
return;
}
add_action('plugins_loaded', function () {
// Shared administrator selection and cookie creation summarized below.
}, 1);
This file does not create a WordPress cron event. The parameter name should not be mistaken for evidence of scheduled execution.
Shared normalized control flow
The following pseudocode preserves the defensively useful logic without providing either operational secret:
helper_id = integer(get_option("wp_helper_uid"))
target_id = 0
for each administrator ordered by ascending ID:
if administrator.ID is not helper_id:
target_id = administrator.ID
stop searching
if target_id is empty and helper_id is nonzero:
target_id = helper_id
if target_id is valid:
set target as current WordPress user
create persistent WordPress authentication cookies
fire the wp_login hook
redirect to wp-admin
Dead code and clustering markers
The comments Widget cache bootstrap, ver b2ccbcd25e and Runtime dependencies bootstrap 9b66554fd3 do not describe the true behavior. They are valuable content-search markers because they appear alongside the backdoor logic.
admin-helper.php also probes three nonexistent-looking function names but never uses the results. These markers, the shared option, and the identical query/cookie sequence provide a stronger signature than either generic filename.
Analyst assessment
This is not a credential checker and not a conventional command-execution shell. It is a narrowly designed authentication bypass implanted on the server. The operator supplies a secret known to the malware, and the malware uses trusted WordPress internals to create a valid session for an administrator chosen from the victim database.
The dual placement matters operationally. Removing the directly visible admin-helper.php leaves the automatically loaded MU-plugin. Removing only the MU-plugin leaves the direct endpoint. The pair therefore provides redundant persistence of access, although the recovered code does not regenerate files and should not be described as self-healing.
Because a forged session receives the target account’s capabilities, the impact can include plugin/theme installation, code editing where enabled, user manipulation, content changes, data access, and further persistence. Multi-factor authentication implemented only in the normal login flow may not protect against a server-side function that creates the cookie after bypassing that flow.
The closest ATT&CK concepts are T1505.003, Web Shell, for a server-side web backdoor providing persistent access, and T1550.004, Web Session Cookie, for authenticated access through session material rather than a password. These are approximate defensive mappings; this sample generates a WordPress session cookie instead of stealing an already issued cookie.
How the malware may have been injected
The recovered files do not contain their installer. Ownership and permission mode only show that the artifacts existed under the hosting subscription’s Unix identity. They do not distinguish among:
- exploitation of a vulnerable plugin, theme, or public upload handler;
- a stolen WordPress administrator session or password followed by plugin/theme editing;
- compromised Plesk File Manager credentials;
- FTP, SFTP, SSH, or deployment credential abuse;
- restoration of an already infected backup;
- a malicious or compromised package; or
- a write from another compromised site running as the same operating-system user.
To establish initial access, preserve current and rotated web logs, Plesk panel history, FTP/SFTP/SSH authentication and transfer logs, WAF records, database audit/binlog evidence, security-plugin alerts, deployment history, and the nearest clean and infected backups. Start at least 72 hours before the earliest trustworthy sighting and correlate source IP, user agent, authenticated account, request route, response status, and file creation evidence.
High-value sequences include an unfamiliar login followed by a code upload, an unauthenticated POST to a component endpoint followed by the first request for a new PHP file, a Plesk/FTP authentication immediately before placement, or the same hashes appearing earlier on a sibling site.
The identical displayed timestamps on the two files are contextual, not conclusive. They can result from one deployment action, a preserved archive timestamp, manual copying, or deliberate timestamp manipulation. No recovered line implements timestomping.
Detection and hunting
Read-only filesystem hunt
Search every document root owned by the same hosting account. rg is preferred when available:
rg -n -I --hidden \
-g '*.php' -g '*.phtml' -g '*.inc' -g '.user.ini' -g '.htaccess' \
'wp_helper_uid|Widget cache bootstrap, ver b2ccbcd25e|Runtime dependencies bootstrap 9b66554fd3|wp_179e4b|wp_4bb239|wp_4960ab' \
/var/www/vhosts/EXAMPLE.COM/httpdocs
Also find the observed filenames, but treat them as candidates until content or hashes match:
find /var/www/vhosts/EXAMPLE.COM/httpdocs -xdev -type f \
\( -name 'admin-helper.php' -o -name 'boot-loader.php' \) -print
Inspect PHP in uploads, cache, upgrade, temporary, and backup directories. Review .user.ini, .htaccess, wp-config.php, all MU plugins, WordPress drop-ins, active and inactive themes, ordinary plugins, and sibling installations.
Read-only database hunt
Replace <prefix>_ with the exact prefix from wp-config.php:
SELECT
option_id,
option_name,
option_value,
autoload,
LENGTH(option_value) AS value_bytes,
SHA2(option_value, 256) AS value_sha256
FROM <prefix>_options
WHERE option_name = 'wp_helper_uid';
Then validate the referenced ID directly rather than trusting the WordPress Users screen:
SELECT u.ID, u.user_login, u.user_email, u.user_registered,
um.meta_key, um.meta_value
FROM <prefix>_users AS u
JOIN <prefix>_usermeta AS um ON um.user_id = u.ID
WHERE u.ID = CAST((
SELECT option_value
FROM <prefix>_options
WHERE option_name = 'wp_helper_uid'
LIMIT 1
) AS UNSIGNED)
ORDER BY um.umeta_id;
For multisite or a nonstandard installation, confirm the correct capabilities key and table layout before drawing conclusions.
Log hunt
Search current and rotated domain logs for the direct filename. Keep full query strings private because they may disclose a usable gate value:
zgrep -Hn -E 'admin-helper\.php' \
/var/www/vhosts/system/EXAMPLE.COM/logs/*
A request match is evidence of access to the path, not automatically successful authentication. Preserve timestamp, status, response size, source/forwarded IP, user agent, referrer, and surrounding lines.
Indicators of compromise
Download the privacy-safe IOC CSV. The operational gate values are withheld; only their SHA-256 hashes are included.
Higher-confidence indicators
| Indicator | Type | Context |
|---|---|---|
4bbeaed0845bcd965c92902a1dfb314d4afd79e48029dc22bf5fa7597a0d93cb | SHA-256 | Exact recovered admin-helper.php |
0cbe6a757abcfe7c8968ae169929164d978f120847c05ca91f5e09c44293c4f3 | SHA-256 | Exact recovered boot-loader.php |
wp_helper_uid | WordPress option | Shared by both files and confirmed in the database |
Widget cache bootstrap, ver b2ccbcd25e | Code marker | Fake header in admin-helper.php |
Runtime dependencies bootstrap 9b66554fd3 | Code marker | Fake header in boot-loader.php |
wp_179e4b, wp_4bb239, and wp_4960ab together | Code probes | Inert clustering markers in the direct endpoint |
8e753f173a5c428fe6c44646cddb0da04952628bcd098400d6fe9fcd3f3bb435 | Secret SHA-256 | Hash of the withheld direct-endpoint gate value |
b6efe58fb91fb0bb9d11fce3b6eb42bcc1b8822deacc39facc657d17cffb1bf5 | Secret SHA-256 | Hash of the withheld MU-plugin gate value |
Contextual indicators
wp-content/admin-helper.phpwp-content/mu-plugins/boot-loader.php- small PHP files using split strings,
WP_User_Query,wp_set_auth_cookie,wp_login, andadmin_url()together; sorcronrequest parameters, which are generic and unsafe as standalone detections; and- matching displayed timestamps or file ownership, which require content or log corroboration.
Filenames and generic request parameters are not sufficient diagnoses. A legitimate developer can choose either filename, and many unrelated applications use a cron parameter.
What this evidence does not establish
- The installer or file-write mechanism was not recovered.
- Initial access is unknown.
- No log proves that an operator successfully used either gate.
- User ID 10 was administrator-capable, but the retained evidence does not establish whether it was maliciously created.
- Empty phpMyAdmin results produced with a generic capability-meta key are not valid evidence that administrators were absent.
- The sample pair contains no C2 address, remote download, command execution, password capture, cron scheduling, file restoration, or sibling-site scanning logic.
- No attribution can be made from attacker-controlled comments, filenames, or variable names.
Artifact-specific remediation
Do not begin by testing either backdoor URL. A successful test creates another valid administrator session and changes the evidence you need to preserve.
1. Preserve evidence and contain execution
- Create filesystem, database, and log snapshots outside the compromised document root.
- Put the virtual host behind a static maintenance response or restrict it at the web-server/firewall layer. A WordPress maintenance plugin is insufficient because the MU-plugin loads during WordPress bootstrap.
- Preserve both samples, metadata, hashes, the
wp_helper_uidrow, referenced user/user-meta rows, all session-token rows, and current/rotated access logs. - Identify every WordPress installation writable by the same operating-system owner.
2. Remove both known access paths
After preservation, remove or quarantine these exact confirmed artifacts together:
wp-content/admin-helper.php
wp-content/mu-plugins/boot-loader.php
Do not delete every file with those names automatically. Verify content or hashes first. Search the entire account for the stable markers and hashes, then remove every confirmed copy.
Inspect the rest of mu-plugins manually. WordPress states that MU-plugin files cannot be disabled through the normal plugin control; the file itself must be removed from the MU-plugin directory.
3. Remove the coordination option
Export the row first. Then, while the site is contained and after confirming the table prefix, remove only the exact option:
DELETE FROM <prefix>_options
WHERE option_name = 'wp_helper_uid';
Deleting the option without removing both PHP files is not a fix: the code can select the first administrator when the option is absent.
4. Audit users before deleting any account
Verify user ID 10 with the site owner. Review its username, email, registration date, authored content, password-change history where available, application passwords, session metadata, and surrounding audit logs.
- If unauthorized, remove it through a clean administrative process and explicitly decide how to reassign any legitimate content.
- If legitimate, retain the account but rotate its password and revoke all sessions.
- Review every other administrator because the backdoors intentionally prefer an administrator different from the stored helper ID.
Do not infer that only user ID 10 was exposed.
5. Destroy sessions and rotate authentication material
After the malicious PHP is gone and WordPress core is trusted, destroy all sessions for all users. WordPress documents wp user session destroy <user> --all, and its CLI documentation provides an all-user example. See wp user session destroy.
Rotate all eight WordPress authentication keys and salts in wp-config.php; WordPress documents that changing them invalidates existing cookies. See the WordPress security-key documentation.
Then rotate:
- every WordPress administrator password;
- Plesk/control-panel credentials and MFA recovery methods;
- FTP, SFTP, SSH, deployment, and repository credentials;
- the database password;
- API, SMTP, payment, backup, and integration secrets accessible to the site.
Use a clean administrator device for the rotations.
6. Rebuild trusted code and investigate the entry route
Replace WordPress core with the exact trusted release and reinstall plugins/themes from verified vendor packages. Diff and separately restore only reviewed custom code and uploads. Do not copy the compromised installation wholesale into the rebuilt site.
Search for PHP in normally non-executable locations and inspect:
- all MU plugins and ordinary plugins;
- active and inactive themes;
wp-config.php,.user.ini,.htaccess, and PHP-FPM/vhost configuration;advanced-cache.php,db.php,object-cache.php, andsunrise.php;- uploads, cache, upgrade,
upgrade-temp-backup, temporary, and backup directories; and - every sibling site under the same hosting identity.
Correlate logs and backups to identify the initial access route. Patch or remove the vulnerable component, compromised account, poisoned deployment, or sibling-site source before returning the site to service.
Recurrence verification
After cleanup:
- confirm both file hashes and all stable markers are absent account-wide;
- confirm
wp_helper_uidis absent from the correct options table; - compare the database-level administrator list with the site owner’s approved inventory;
- confirm all old WordPress sessions are invalid;
- verify the MU-plugin directory contains only documented, trusted files;
- monitor requests for
admin-helper.phpwithout responding through WordPress; - alert on recreation of either path or option;
- monitor new administrator capability assignments, plugin/theme writes, and PHP created in uploads/cache; and
- repeat the hunt after PHP/web-service restart and again after normal site traffic resumes.
If either file or option returns, stop request processing again and search for the missing writer. The current samples do not self-repair, so recurrence would be evidence of another surviving component, stolen access, scheduled deployment, infected backup, or compromised sibling site.
Related malware research
Browse the WordPress malware research hub.
- wp-user-query.php MU-Plugin Concealing a Stored User ID
- Hidden Administrator Query Hooks Found in functions.php
- WP Compatibility Patch Plugin Creating and Hiding an Administrator
Related guides and case studies
- How to find hidden WordPress administrators
- How to investigate a hidden WordPress backdoor
- WordPress malware removal guide
- MU-plugin hidden-user case study
Methodology and privacy note
The samples were handled as text and were never invoked through WordPress. File hashes were calculated locally, and PHP linting validated syntax without executing application logic. Official WordPress documentation was used to verify MU-plugin loading, cookie duration, session-token creation, session destruction, and salt rotation.
Public screenshots are privacy-reviewed derivatives. Client account-owner values, the database name/table prefix, and the user nickname were removed. Query-only and empty-result screenshots were excluded because they added no reliable finding; one empty capability query used a generic meta-key prefix and was not suitable for interpretation.
Both hard-coded gate values are withheld. The report publishes only their SHA-256 hashes, exact sample hashes, non-operational code markers, and redacted defensive excerpts. Confirmed behavior is separated from inference, and the report does not claim an entry vulnerability, successful operator use, self-healing, campaign prevalence, or attribution without supporting evidence.
Support My WordPress Security Research ☕
If this work helped you and you would like to support future malware investigations and practical guides, you can buy me a coffee. It is completely optional—everything remains free to read.