Security audit report

northgate-advisors.com

Full security review of the site files, configuration, database and access history.

Project 100472 Site northgate-advisors.com Report date 30 July 2026 WordPress 7.0.2 Table prefix wp_ Server nginx

The site was compromised in July 2026, and it is still exposed today: High Risk

Three administrator accounts created by an attacker logged into the site between 22 and 23 July, and a webshell was found in the media folder on 28 July. Those artefacts have since been removed. What remains open is more urgent: your database password, all of your security keys and your mail API key are downloadable by anyone at a public address, close to 8,000 customer documents can be fetched without logging in, and the media folder still runs PHP code, which is exactly what made the backdoor work.

This is a sample. It is a real audit with every identifying detail replaced: the site name, the project number, all account names and addresses, the staff names the password campaign was targeting, and the customer document paths. The findings, counts, dates and reasoning are unchanged, so it reads exactly like the report a customer receives.

Overview

Severity counts and the state of the site as reviewed on 30 July 2026.

5
Critical
6
High
9
Medium
4
Low
7,994
Customer files exposed
0
Malicious files remaining
Read this first. Two different things are described in this report and they should not be confused. The intrusion itself (attacker accounts, backdoor file) appears to have been dealt with: nothing malicious is on the site today. The exposure problems (credentials and customer documents readable by anyone, code execution allowed in the media folder) are live right now, were verified from the public internet on 30 July 2026, and are what would let the same thing happen again.

Site inventory

ItemValue
Site addresshttps://northgate-advisors.com
WordPress version7.0.2
Web servernginx
Database table prefixwp_ (default)
StackThrive Theme (child theme active), WooCommerce and Subscriptions, Amelia booking, Fluent suite (Forms, CRM, Support, SMTP), Formidable, Rank Math Pro, Real Cookie Banner Pro, Wordfence, Patchstack
PHP files47,005
Database tables355
User accounts317 (8 administrators)
Plugins54 active, 63 present on disk
Themes7 (thrive-theme-child active)
Total size4.8 GB
Hosting context. The site has moved hosts twice. Three different server paths are still recorded in the database, an older Plesk path, an intermediate one, and the current one. That history explains several things that would otherwise look like an attacker covering their tracks: a security plugin whose folder was deleted but whose scheduled tasks remain, a half applied database prefix rename, and leftover settings from tools that are no longer installed. Those are treated as housekeeping in this report, not as intrusion evidence.

What happened

Reconstructed from the site's access records and its security plugin logs. All times are UTC. Two spans matter more than the individual events: the way in was never recorded, and the alert that named the backdoor went unread for six days.

Way in: not recorded
Alert unread, 6 days
not recorded
22July
23July
24July
25July
26July
27July
28July
29July
30July
Attacker activity Your team Detected by your tools State on the report date

Point at a marker to see what it was, or click it to jump to the detail below.

  1. Before22 July
    The site is compromised. Three administrator accounts are created that nobody on your team recognises. The route in is not recorded. What made it possible is: four plugins running versions with published security flaws, and a media folder that accepts and runs PHP code.
  2. 22 July03:20 UTC
    First attacker login. Account a4f2_71c9d038 (address at shellcode.lol) signs in from 203.0.113.41 on the first attempt, with no failed tries before it. A first-attempt success means the account was created by whoever used it, not guessed.
  3. 23 July03:20 UTC
    Second attacker login. Account 8b16f402c7de signs in from 203.0.113.88.
  4. 23 July04:48 UTC
    Third attacker login. Account site_admin (address at shelltool.invalid) signs in from 198.51.100.23.
  5. 23 to 26 July
    Your team removes the three accounts. Deleted one at a time from 198.51.100.9. No unexpected administrator remains today.
  6. 27 July15:00 UTC
    A change in the protective server rules is recorded. The rule that should stop PHP from running inside the media folder is not in effect.
  7. 28 July01:03 UTC
    Your own malware scan reports the backdoor. Flagged at wp-content/uploads/_9ce8098b.php, rated maximum severity. Email alerting was switched on and correctly configured at the time, so a notification should have reached your agency's operations address.
  8. 30 July
    Where things stand. The backdoor file is gone and no malicious code was found anywhere on the site. The alert is still sitting unacknowledged in the scanner, and the conditions that let the backdoor run are unchanged.
On dates. File timestamps are normalised and cannot date the intrusion. The times above come from login records and scanner history, which are reliable. Your host's web server access logs would show the exact request that dropped the backdoor file, and they are worth requesting while they are still retained.

Indicators of compromise

The concrete values behind this incident, each with whether it is still true today and what to do with it. Two of the seven are live right now. The rest are worth checking on any other site you run, since a copied site carries its leftovers with it.

Blocklist and search list
Backdoor file
wp-content/uploads/_9ce8098b.phpRemoved

We requested this address on 30 July and the server answered "not found". Search your other sites for the same path, and for any PHP file sitting directly in the media folder.

Backdoor fingerprint
03E3772B0E1670743301179E66A1505B16E687F92EA45092F05F198A54BD6382Reference value

The identity card of that exact file, unaffected by renaming it. Give it to your host or paste it into your security plugin's file search to find the same file on another site.

Attacker accounts
a4f2_71c9d038@shellops.invalid, 8b16f402c7de@mailbox.invalid, site_admin@shelltool.invalidDeleted here

All three were removed from this site between 23 and 26 July. Open the user list of every other site you run and search for these three addresses, and for anything ending in shellcode.lol or shelltool.invalid.

Attacker addresses
203.0.113.41, 203.0.113.88, 198.51.100.23No longer active here

The three addresses the attacker accounts logged in from. Nothing has come from them since 23 July. Block them at your firewall and search your other sites' login history for them.

Unidentified session source
192.0.2.117Still live today

This one is not history. Eight administrator sessions from this address are open right now and valid until 11 August. Ask your agency and your app developer today whether it is theirs. If nobody claims it, treat the site as still reachable by an outsider (H-2).

Leaking address
/wp-config.php.wpdebugtoolkit-backupStill live today

Served to anyone who asks, confirmed on 30 July. Delete this file today, then change everything it contained: the database password, the nine keys and salts, and the Mailgun key (C-1).

Naming pattern
underscore, then 8 hexadecimal characters, PHP file in the media folder, contents starting with GIF89aWhat to look for

How a sibling of this backdoor would look elsewhere. List the contents of the media folder on your other sites and treat any PHP file there as suspect, whatever it is called.

Findings

24 findings, most severe first. Use the filter in the bar above to narrow by severity.

Critical C-1 https://northgate-advisors.com/wp-config.php.wpdebugtoolkit-backup

Your database password, all security keys and your mail API key are downloadable by anyone

A copy of the main WordPress configuration file was left in the public folder under a different file extension. Because the extension is not .php, the server does not execute it, it hands over the text. Requesting that address from the internet returns the whole file, and it contains the database name, database user, database password, all nine authentication keys and salts, and the Mailgun sending key. We confirmed the file is served on 30 July 2026 and did not keep a copy of its contents.

What this means in practice. The mail key lets anyone send email that appears to come from your domain, which is a ready made phishing capability against your own customers. The authentication keys let an attacker tamper with sessions and forms. The database password is limited by the fact that your database only accepts connections from the server itself and the database port is not reachable from outside, which we checked, so it is not directly usable from the internet, but it becomes immediately usable to anyone who gets any foothold on that server.

Action: delete that file today, then rotate everything it contained: the database password, all nine authentication keys and salts (this logs everyone out, which is intended), and the Mailgun API key. Then check the rest of the public folder for other files whose names start with wp-config.
Critical C-2 uploads/client-statements/, billing_invoices_temp/, uploads/fluentform/, uploads/fluent-support/

7,994 customer documents can be downloaded without logging in

Four separate folders hold customer documents that the web server hands to anyone who asks. We confirmed one file from each set downloads successfully from the public internet, and kept none of them.

  • 5,606 tax documents in uploads/client-statements/, named in a simple sequence (statement_5393.pdf, statement_3084.pdf). Nothing protects the folder and the numbering is guessable, so the whole range can be walked. Automated scanners do this routinely.
  • 27 invoices in wp-content/billing_invoices_temp/, named invoice-4395.pdf and similar, equally guessable. The folder does contain an access rule, but it is written for Apache and your site runs on nginx, so it does nothing.
  • 1,113 files submitted through your forms in uploads/fluentform/.
  • 1,248 attachments across 248 support ticket folders in uploads/fluent-support/. These are the most sensitive of the four sets: the file names alone, visible in the address, announce tax certificates, bank statements, insurance policies and investment account summaries, each next to a customer surname.

One distinction matters for how urgent each set is. The tax documents and the invoices are numbered in sequence, so anyone can enumerate them without knowing anything about your site. The form and ticket files carry a random string in their names, so they cannot be guessed one by one, but nothing stops access either: the name is enough, and names leak through browser history, referrer headers, shared links, and to anyone who reaches the database. Obscurity is not protection.

This is customer tax, billing and correspondence data, which under the GDPR is sensitive personal data. Treat the possibility of unauthorised access as real rather than theoretical, since the site was also under attacker control in the same period.

Action: block direct access to all four folders at the web server level today, not through a plugin rule. Serve these documents only through a script that checks who is asking. Then ask your host for the access logs of those folders so you can establish whether anyone actually downloaded them, which is the fact you will need if a notification decision has to be made.
Critical C-3 wp-content/uploads/

The media folder runs PHP code, which is what made the backdoor work, and it is still open

Your security plugin does try to stop PHP files from running inside the media folder, but it does that with an .htaccess rule, and .htaccess files are an Apache feature that nginx ignores completely. Every rule of that kind on this site is inert, including this one.

We verified this directly. An existing PHP file in the media folder was requested from the internet and the server executed it rather than returning its text. Your site's own protective rule check recorded the same conclusion on 27 July: PHP execution in the media folder is not blocked.

This matters more than it sounds. The backdoor found on 28 July was a PHP file in exactly this folder, disguised with a GIF89a header so it would pass as an image on upload. Deleting that file removed the symptom. As long as uploads can execute, the next file upload flaw in any of your 54 plugins produces the same result again.

Action: add an nginx rule that refuses to pass any request under wp-content/uploads/ to PHP. This is a few lines in the server configuration and it is the single most valuable change on this list. Do not rely on a plugin setting for it.
Critical C-4 User accounts 372, 374 and 376 (all deleted)

Three attacker created administrator accounts had full control of the site

Three accounts with full administrator rights logged into the site between 22 and 23 July. Their email addresses name the tooling used: shellcode.lol, shelltool.invalid, and a machine generated name at a plausible looking domain. Each logged in successfully on the first attempt with no failed attempts before it, which rules out password guessing and means the accounts were created by whoever then used them.

Your team deleted all three between 23 and 26 July, and no unexpected administrator remains today. That is the right outcome, but an account with administrator rights can do anything before it is removed: install a plugin, edit theme files, read the whole database, export customer data. Assume all of that was possible during those days.

Action: reset the password of every remaining administrator and force all sessions to end, which the key rotation in C-1 also achieves. Enable two factor authentication on all 8 administrator accounts, since only one has it today.
Critical C-5 wp-content/uploads/_9ce8098b.php (removed since)

A maximum severity backdoor was reported by your own scanner and the alert was never acted on

On 28 July at 01:03 your site's malware scan flagged a file in the media folder as malicious at severity 100. The file was disguised to look like an image (it began with the text GIF89a) and its code assembled the names of dangerous functions letter by letter to slip past scanners. Those functions were the ones for accepting an uploaded file, writing files to disk, and running system commands. In plain terms: a tool for uploading anything and running commands on your server.

The file is gone from the site today and we confirmed the address now returns a not found response. The alert, however, is still recorded as new and unhandled, and alerting was switched on with a valid destination address at the time, so someone should have received an email about it on 28 July. The same scan also lists 35 outdated plugins and themes, none of which have been acknowledged either.

Action: confirm with your agency who receives the security alert emails and whether the 28 July message arrived. Then run a fresh scan and work the issue list to zero, rather than leaving alerts open, because an unread alert list is indistinguishable from having no alerting at all.
High H-1 https://northgate-advisors.com/wpdebugtoolkit/ plus wp-content/db.php

A debug console that records every database query is reachable at a public address

There is a self contained diagnostic tool installed at the top level of your site. We confirmed the address answers on 30 July. It is not part of WordPress and it does not use WordPress logins, it is protected by one single password of its own. The tool can read your debug log, can rewrite your main configuration file, and records the full text of database queries, which is why SAVEQUERIES is switched on in your configuration and why a database interception file sits in wp-content.

Database query text is not harmless content. It contains password hashes, session identifiers and whatever your forms submit, which for this site includes customer data. Two further details make this worse: the configuration backup that leaks your credentials in C-1 was created by this same tool, and your malware scanner is explicitly configured to skip this directory, so it is the one place on the site nothing is checking.

We reviewed its code and found no hidden malicious behaviour: the sensitive actions are behind its password and a request token. The problem is not that it is malicious, it is that it exists on a production site handling tax data.

Action: remove the tool, the wp-content/db.php interception file and the SAVEQUERIES setting from the live site. If it is genuinely needed, restrict it to your office IP addresses at the server level and stop excluding it from malware scans.
High H-2 Account 1 (admin_k2p9x1), source 192.0.2.117

Eight administrator sessions from a server nobody has identified, valid until 11 August

Your main administrator account currently holds eight concurrent logged in sessions, all from the address 192.0.2.117 (a hosting provider, not an office connection), all identifying themselves as node rather than a browser. They were created in pairs on 23, 24, 26 and 28 July and remain valid until 11 August.

These were not created by someone typing a password: that account's stored password is still in the older format, and WordPress upgrades the format automatically the first time a correct password is used. So something is creating administrator sessions programmatically. We ruled out the WP Umbrella management connection, which uses different addresses and works differently.

The most likely explanation is your own integration, given that a user account named PortalApp exists on the site. That would make this expected behaviour with poor hygiene. The alternative is that it is not yours, in which case it is standing administrator access. We cannot tell these apart from the outside, and the window during which attacker accounts were active overlaps these dates.

Action: ask your agency and your app developer whether 192.0.2.117 is theirs, today. If nobody claims it, treat the site as still accessible to an outsider. Either way, end all sessions on that account and have integrations use a dedicated account with only the permissions they need, never the main administrator.
High H-3 Fluent Forms 6.2.5, Google Tag Manager 1.22.3, Loco Translate 2.8.5, Widget for Google Reviews 6.9.7

Four plugins are running versions with published security flaws, and 35 updates are pending

Your scanner flags those four as running a version for which a vulnerability has been published, meaning the technical details are public. In total 35 plugins and themes are behind, including the whole Thrive suite, WooCommerce, Amelia and the Fluent suite.

This is the most probable way in. The backdoor arrived as an image looking file in the media folder, which is the signature of an upload handling flaw, and Fluent Forms is the component on this site that accepts customer file uploads. We cannot prove which flaw was used without your server access logs, but leaving these versions in place keeps the door in the same state it was in on 22 July.

Action: update those four first, then work through the remaining 31. Automatic core updates are currently switched off in your configuration, so this depends entirely on the managed update process running on schedule.
High H-4 wp-config.php, DISALLOW_FILE_EDIT not defined

The built in file editor is enabled, so any administrator session can run code

WordPress ships an editor that lets an administrator rewrite theme and plugin files from the browser. On this site it is enabled. That turns any administrator session into the ability to run arbitrary code, with no upload and no file transfer needed.

Given that three attacker administrator accounts were logged in during July, this was available to them. It should be switched off permanently: legitimate changes go through your agency's deployment process, not the browser editor.

Action: add define('DISALLOW_FILE_EDIT', true); to the configuration file. Also add define('FORCE_SSL_ADMIN', true);, which is currently missing.
High H-5 Login records, 393 failures from 221 addresses in seven days

A sustained password attack is running against your real staff names, and 7 of 8 administrators have no second factor

Over the last seven days the site recorded 393 failed logins from 221 distinct addresses, plus 56 attempts stopped by the login protection and 21 attempts to enumerate usernames. Over the longer plugin log the campaign is larger: the display name Daniel was targeted around 500 times and Rossi Marta around 179.

The important detail is that these are real names of real people on your site, not the usual admin guesses. That means the attackers scraped author names from your pages and are working through passwords for known accounts. One address per name, spread thin, deliberately slow enough to look like ordinary traffic.

Exactly one of your eight administrator accounts has two factor authentication enabled. For the other seven, a correct password is the only thing standing in the way, and passwords are the one thing this campaign is designed to eventually get right.

Action: require two factor authentication for every administrator and shop manager account. Hide author names from public pages so the attacker list stops being refreshed, and keep the login protection on.
High H-6 wp-content/share-one-drive-cache/

Your OneDrive access token folder is publicly readable

The OneDrive integration caches its authorisation data in a folder with no access control, and we confirmed a file in it is served over the internet. The filename alone already discloses the connected account address and your Microsoft tenant identifier. In the state we reviewed, the token file itself was empty, but on a live site this path is where a valid access token to your OneDrive sits.

A valid token of that kind is read and write access to the connected document storage, without any password and without triggering a login notification.

Action: deny web access to that folder at the server level, then disconnect and reconnect the OneDrive integration so any token that may have been exposed is replaced.
Medium M-1 User accounts 1, 2, 8, 11, 12, 23, 70, 325

Eight administrator accounts, two of them unused for months

Eight accounts hold full administrator rights on a site processing tax and payment data. All eight resolve to people or organisations you work with, so none is a rogue account, but two stand out as dormant.

  • An agency account created in October 2022, unused since January 2025, whose stored password is still in the old format.
  • A vendor support account created on 21 May 2026, used once from an address in Bangladesh, unused since. The address is consistent with the support team of one of your plugin vendors, so it is probably legitimate access your agency arranged, but it should not still be open.

Every dormant administrator is a full set of keys nobody is watching.

Action: remove or downgrade the two dormant accounts, and agree with your agency that vendor support access is granted for a specific request and revoked afterwards.
Medium M-2 MainWP Child, present on disk but inactive, settings and scheduled tasks remain

A remote management channel is still wired to two administrator accounts although its plugin is switched off

A remote site management plugin remains installed but deactivated, and two administrator accounts still carry its connection settings, including a stored connection key. Its scheduled tasks are still registered, and it left a server clone folder inside the media directory.

A tool like this is a remote administration channel by design. Deactivated is not removed: the files are still on disk and reactivation restores the channel. It is most likely your agency's own management setup, which is fine, but it should be either in use and current, or gone.

Action: confirm with your agency whether this channel is still needed. If not, delete the plugin folders, remove the connection settings from both accounts and clear the leftover scheduled tasks.
Medium M-3 wp-content/uploads/cache/wpml/twig/ (91 files) and wp-content/plugins/share-one-drive-1/

Executable code and an old duplicate plugin copy are sitting in folders that should hold neither

Ninety one compiled PHP template files were left inside the media folder by a translation plugin that is no longer installed. Their content is harmless, but they are executable code in the one folder that should never contain any, and as C-3 establishes, that folder does run PHP on this server.

Separately, a complete second copy of the OneDrive plugin is present at version 3.5.2.1 while the live copy is 3.8.1. It is not active, but every file in it is still reachable and executable, which means an old version's flaws remain available even though the plugin appears updated.

Action: delete both trees. Old plugin copies should never be parked inside the plugins folder.
Medium M-4 /.user.ini, /.htaccess, /wp-content/debug.log

Your server configuration files and your debug log are publicly readable

Three files that describe how your server is set up are served to anyone who asks. We confirmed all three on 30 July:

  • /.user.ini (112 bytes), which your scanner already reported on 27 July and which is still listed as unresolved. It discloses the absolute path of the site on disk.
  • /.htaccess (1,901 bytes), which hands an attacker your complete rule set, including which paths you believe are protected and which security plugin wrote the rules.
  • /wp-content/debug.log (220 bytes), which is where PHP errors are written. Debug logs routinely contain file paths, query fragments and occasionally submitted data.

None of these is dangerous by itself. Together they are the reconnaissance an attacker does before choosing a technique, and they are collected automatically by scanning tools.

Action: deny web access to files beginning with a dot, and to .log files, at the server level. On nginx that is two rules and it covers these three files and many similar ones at once.
Medium M-5 24 .htaccess files across the site, server confirmed as nginx

Every protective rule this site relies on is written for the wrong web server

We found 24 access rule files across the site, written by your security, backup and form plugins. All of them use Apache syntax. Your server is nginx, which ignores these files entirely.

The consequence is systematic rather than isolated: the block on PHP in the media folder (C-3), the protection of the configuration file, the Deny from all on the invoice folder (C-2) and on the backup staging folder all read as active protections in a plugin dashboard while doing nothing at all. This is the single reason several findings in this report are as severe as they are.

Action: translate the rules that matter into your nginx configuration: no PHP under the media folder, no access to dot files, no access to the document and invoice folders, no access to backup folders. Then stop treating the plugin dashboards as evidence that these protections are in place.
Medium M-6 Registration setting enabled, default role subscriber

Anyone can register an account on the site

Open registration is on, with new accounts receiving the subscriber role. That is plausibly deliberate given the shop, the booking system and the online courses, and the account list is consistent with genuine customers: the February and March registration peaks match the tax filing season.

It is listed here because an open registration form is the standard first step in privilege escalation attacks. Several of the plugins on this site have historically had flaws that only a logged in user could exploit, and open registration means anyone can become one.

A handful of accounts use disposable or anonymous email domains. None of them holds any privileges.

Action: keep registration if the business needs it, and in exchange keep plugins current, since that is what the risk depends on. If it is not needed, switch it off.
Medium M-7 File manager key, backup plugin HTTP authentication settings

Credentials from uninstalled plugins are still stored in the database

Two plugins that are no longer installed left credentials behind: a file manager left its access key, and a backup plugin left stored HTTP authentication settings. We did not read the values.

A file manager is worth noting for a second reason. It gives full filesystem access from the browser, and traces of one are present in several places on this site, so it did run here at some point. That is the kind of tool that turns a single compromised administrator account into complete server access.

Action: delete these leftover settings, and if either credential was reused anywhere else, change it.
Medium M-8 wp-config.php

Configuration weaknesses that make an incident worse than it needs to be

Three settings, none dangerous alone, each of which widened the impact of what happened.

  • Full database query logging is switched on in production, which is what fills the query log described in H-1 with password hashes and customer data.
  • The default wp_ table prefix, which lets automated attack tools guess your table names without probing.
  • Automatic updates are switched off, so the site is entirely dependent on the managed update schedule, and 35 updates are currently pending.

Your database credentials themselves are sound in shape: the password is not empty or trivially short, and the security keys are unique rather than left at their placeholder values. They now need changing only because C-1 published them.

Action: switch off query logging on the live site, and either re enable automatic security updates or confirm that the managed schedule is actually applying them.
Medium M-9 /wp-content/updraft/ and /wp-content/wpvividbackups/

A backup plugin folder answers on the public web, and we cannot see what is in it

Both addresses respond rather than refusing the request. That matters more than it looks: backup plugins write full site archives into these folders, and a single archive contains the entire database, meaning every password hash, every customer record and every key on the site in one downloadable file.

We are being explicit about the limit of what we can say here. The contents of these folders are outside what this review can see, so we are not reporting them as exposed and not reporting them as clean. What we know is that the paths are live and that nothing refused us.

Action: list both folders on the server today. If either holds an archive, treat it as a credential exposure of the same order as C-1 and move the archives off the web root. Then deny web access to both paths at the server level, since the plugin rules that were supposed to do it are Apache rules on an nginx server.
Low L-1 Comments table, 28 entries

Comment spam in the moderation queue

Twenty eight comments linking to gambling and adult sites are sitting in the spam queue. The filter caught all of them, none was ever published, and none of your pages contains this content. This is ordinary inbound spam, listed only so you know it was checked.

Action: empty the spam queue. No further action needed.
Low L-2 Ten embedded frames referencing test1.northgate-advisors.com

Content still points at a staging address

Ten embedded frames inside page builder content still point at a staging subdomain. Not a security flaw in itself, but worth checking that the staging site is not publicly reachable, since a copy of a site usually holds the same data with none of the maintenance.

Action: repoint the content at the live site, and put the staging site behind a password.
Low L-3 Scheduled tasks, cache folders, stray root files

Leftovers from two hosting migrations and removed plugins

The site carries a visible history: scheduled tasks belonging to a security plugin, a translation plugin, an activity log and a management tool that are all long gone, an option name still carrying a former table prefix, several stale cache folders, a leftover maintenance page referencing the oldest server path, and a copy of an index file. Fourteen randomly named folders in wp-content look alarming and are in fact the cookie banner plugin's asset cache, containing only JavaScript, twelve of them empty.

None of this is malicious. It matters because clutter is where malicious files hide, and because a person reviewing this site in a hurry cannot tell the difference.

Action: clear the orphaned scheduled tasks and delete the stale folders and stray files. Worth doing once, properly.
Low L-4 Database level triggers, procedures and scheduled events

One database check cannot be completed from the outside

Malware can occasionally hide inside the database engine itself, as a trigger or a stored procedure that reinstalls a payload whenever a page loads. This is the one category of hiding place that cannot be ruled out from the material available, so it is reported as unresolved rather than clean.

Three queries settle it in seconds, run against the live database:

SELECT TRIGGER_NAME, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT
  FROM information_schema.TRIGGERS;
SELECT ROUTINE_NAME, ROUTINE_TYPE, ROUTINE_DEFINITION
  FROM information_schema.ROUTINES;
SELECT EVENT_NAME, STATUS, EVENT_DEFINITION
  FROM information_schema.EVENTS;

On a normal WordPress site all three return nothing. Anything listed should be read carefully.

Action: have your host or agency run those three queries and send you the output.

Addresses we confirmed are publicly reachable

Each of these was requested from the public internet on 30 July 2026 and answered. No file contents were retained.

Verified exposed addresses 11 addresses
  • /wp-config.php.wpdebugtoolkit-backupdatabase password, all keys, mail key
  • /wp-content/uploads/client-statements/statement_5393.pdf1 of 5,606 tax documents
  • /wp-content/billing_invoices_temp/invoice-4395.pdf1 of 27 invoices
  • /wp-content/uploads/fluentform/1,113 form submissions, one verified
  • /wp-content/uploads/fluent-support/ticket_140/1 of 1,248 ticket attachments
  • /wpdebugtoolkit/query log console
  • /wp-content/share-one-drive-cache/1.shortcodestoken folder readable
  • /.user.iniserver paths disclosed
  • /.htaccess1,901 bytes, full rule set disclosed
  • /wp-content/debug.logPHP error log readable
  • /wp-content/updraft/backup folder answers, contents unknown
One more result is worth recording as good news. PHP execution in the media folder was tested with a file that already existed there and it ran, which is finding C-3. Nothing was uploaded to your site during this review, and nothing was changed.

Checks that passed

What came back clean, stated explicitly, because knowing what was ruled out matters as much as the findings.

Passed 47,005 PHP files reviewed

No malicious code anywhere in the site files

Every PHP file was checked for hidden and scrambled code, for code that runs whatever a visitor sends it, and for the small loaders that reinstall deleted malware. Nothing was found. WordPress core, the active theme, the three automatically loaded plugins and the site's entry files are all as the vendors ship them, with no inserted code.

The two places on this site that can run arbitrary code are legitimate tools, reviewed line by line: the code snippet manager and the diagnostic console in H-1. Both keep that capability behind an administrator check.

Passed Posts, page data, comments, links

Your pages and content are clean, no injected links, no spam

No injected scripts, no hidden links, no search engine spam and no redirect code in any page or page setting. Every embedded frame and script resolves to something expected: your own site, your analytics, your consent banner, Google Maps, YouTube and a financial modelling tool. Outbound links point at Swiss cantonal and federal tax authorities, Wikipedia and similar. Nothing in your published content was touched.

Passed Settings, scheduled tasks, code snippets, active plugin list

No hidden persistence in the database

This is the check that matters most after a compromise, because it is what survives a file cleanup. All ten stored code snippets were read in full and all are your own or your agency's legitimate work. No scheduled task contains code. No setting holds an encoded payload. Every active plugin has a matching folder on disk, so there is no hidden plugin running from the database alone. Your site address settings are correct and there is no pending administrator email change.

Every plugin update source points at its real vendor, and there is no sign of pirated plugin copies, which are a common way for malware to arrive with a site owner's own consent.

Passed Mail history and campaign volume

Your site is not being used to send spam

Mail volume is a steady 11,000 to 14,000 messages a month over more than three years, matching your newsletter schedule, with the usual January and July peaks. There is no burst and no unexplained sending, so the attacker access in July was not used to turn the site into a spam relay. That also means your domain's sending reputation is intact.

Passed 317 accounts reviewed

No unexpected administrator remains, and no hidden privilege grants

All 317 accounts were reviewed against their stored permissions. The eight administrators all resolve to your team, your agency or a plugin vendor, and no account carries permissions granted outside the normal mechanism. The customer registration pattern is consistent with your business, including the tax season peak. The three attacker accounts described in C-4 are confirmed gone.

What to do, in order

Ordered by what reduces the most risk soonest. The first four are for today.

  1. Delete the leaked configuration copy and rotate what it exposed. Remove wp-config.php.wpdebugtoolkit-backup, then change the database password, regenerate all nine authentication keys and salts, and issue a new Mailgun API key. The rotation is the part that matters: the file has been readable, so those values must be treated as known.
  2. Close public access to the customer documents. Block uploads/client-statements/, billing_invoices_temp/, uploads/fluentform/ and uploads/fluent-support/ at the web server, then serve those files only through a script that verifies who is asking. Ask your host for the access logs of those folders so you can establish whether anyone actually fetched them.
  3. Stop PHP from running in the media folder. One nginx rule. This is the condition that turned an uploaded file into a working backdoor, and it is still in place.
  4. Identify 192.0.2.117 and end all administrator sessions. If nobody claims that address, assume an outsider still has administrator access and act accordingly. Ending all sessions happens automatically when you rotate the keys in step 1.
  5. Update the four plugins with published vulnerabilities (Fluent Forms, Google Tag Manager, Loco Translate, Widget for Google Reviews), then work through the remaining 31 pending updates.
  6. Require two factor authentication on all eight administrator accounts and reset their passwords. Only one has a second factor today, while a password attack is actively running against your staff names.
  7. Disable the file editor. Add DISALLOW_FILE_EDIT and FORCE_SSL_ADMIN to the configuration file.
  8. Remove the diagnostic console from the live site, along with the database interception file and the query logging setting. If it must stay, restrict it by IP address and stop excluding it from malware scans.
  9. Translate the protective rules into nginx. All 24 access rule files on this site are written for Apache and do nothing. Until they are translated, several protections you believe are active are not.
  10. Reduce the administrator count and close the remote management channel, or confirm it is in active use. Remove the dormant agency and vendor accounts.
  11. Clean the leftovers: the compiled template files in the media folder, the duplicate old plugin copy, the OneDrive token folder exposure, the credentials left by removed plugins, and the orphaned scheduled tasks.
  12. Ask your host for web server access logs covering 15 to 28 July. This is the one source that can show the request that dropped the backdoor, which is what tells you the entry point is genuinely closed rather than merely quiet. Request it soon, since retention windows are usually short.

What keeps this from happening again

Ten of the findings above are answered by something already in your WP Umbrella dashboard. Three are being watched today. The other seven are one setting away, and each row below links straight to the page.

3
Already watched for you
7
One setting away
Switch on today
Not in effect here Live, alerts within days Open Activity Log

Turn on the Activity Log

It records logins, new accounts, administrator promotions, setting changes and file edits, each with the address and the time, kept off your site so nothing on the site can erase it.

Two automatic checks report into it. One flags an administrator account WordPress did not create, which is exactly what the three July accounts were (C-4). The other tests whether your server really honours your access rules, which is M-5, and therefore why the media folder still runs PHP (C-3). It would also have named who is behind the eight sessions from 192.0.2.117 (H-2).

Answers C-4H-2M-5C-3
Not in effect here From the next page load Open Hardening

Turn on Rate-limit login attempts and Block user enumeration

The campaign in H-5 only works because it has your staff's real names. Blocking enumeration stops your author pages and REST API handing them out, and the rate limit locks an address out after a handful of failures. Turn on Mask login error messages on the same page while you are there.

None of the three replaces the two factor authentication H-5 asks for, which we do not provide.

Answers H-5
Not in effect here From the next page load Open Hardening

Turn on Disable the theme and plugin file editor

This is the exact fix H-4 describes, as one toggle instead of an edit to wp-config.php. An administrator session with that editor available can rewrite any theme or plugin file straight from the browser, which the July accounts could have done.

Answers H-4
Not in effect here On the schedule you set Open Plugins

Turn on automated updates

They apply updates on a schedule, photograph your key pages before and after, and roll an update back when the pages change. That last part is what removes the reason 35 updates were allowed to pile up (M-8).

Answers M-8
Already working for you
Active Live, as advisories publish Open Vulnerabilities

Your vulnerability list is already there

The four plugins named in H-3 are on that page with their advisory, and were before this review started.

So was the severity 100 backdoor alert on 28 July. Neither was read. Agree with your agency who checks this page and how often, because that is the one part no setting does for you.

Answers H-3
Active On every check Open Site health

Deactivated plugins and themes are already counted

Both the remote management plugin in M-2 and the older duplicate OneDrive copy in M-3 appear in that count.

It tells you they exist. It does not tell you M-2 still holds a connection key wired to two administrator accounts, which is why it is a finding here and not just a number.

Answers M-2M-3
What this does not claim. None of these would have stopped the July intrusion on its own. What they change is how long something runs before anyone knows. Here that was six days between the backdoor alert and anyone reading it, and about a year between your protective rules stopping working and this report saying so.

Want us to clean it for you?

If you would rather not work through this list yourself, we can carry out the remediation.

Managed cleanup
We close the exposure and harden the site
€180one-time intervention

What we do

  • Contain first: remove the leaked configuration copy and end every active session.
  • Rotate the database password, all authentication keys and the mail API key.
  • Close public access to the tax documents, the invoices, the form and ticket attachments, and the token folder at the server level.
  • Block PHP execution in the media folder with a server rule that does not depend on plugin settings.
  • Reinstall WordPress core and every plugin from clean vendor sources, and apply the pending updates.
  • Remove the diagnostic console, the duplicate plugin copy and the leftover executable files.
  • Re-scan the whole site and hand you a before and after report.
  • Harden: file editor off, forced HTTPS in the administration area, two factor authentication set up for the administrators.

What we need from you

  • SFTP or FTP access to the site.

That is all we need to get started. Everything else (credential rotation, closing the entry point, hardening) is handled as part of the cleanup.

Obligation of means. We apply every step but cannot guarantee 100% against reinfection until the entry vector is closed. On this site the likely entry vector is an outdated plugin, so keeping updates current after the cleanup is part of what keeps it closed.
No findings at this severity.