Ravensdale Digital Services
Website Maintenance

WordPress Redirect Malware: Fake Maintenance Page Cleanup

An anonymised cleanup case study: tracing delayed redirects through the database, mobile-targeted scripts and eight fake maintenance files.

By Ravensdale Digital Team29 October20 min read
WordPress case study: Redirect malware cleaned. Browsing restored. Desktop and mobile pages with a shield and blocked redirect paths.
Jump to section
Share this article

The WordPress site opened, then redirected visitors to spam

The website loaded its content. A few seconds later, the browser left for an unrelated address.

That was the starting point for a recent WordPress malware cleanup at Ravensdale Digital. The original redirect affected desktop and mobile visitors. The injected code included a seven-second meta refresh alongside JavaScript that could send mobile visitors away sooner.

The first database repairs restored the homepage address and removed injected content. The site appeared to open normally again. Later mobile testing exposed another redirect, this time in JavaScript loaded by the page.

The investigation covered 677 affected content records, an altered SEO setting, generated cache files, suspicious PHP directories, three infected carousel scripts and eight identical fake maintenance files. The client is anonymised throughout this case study.

Here is what we found and how we worked through it. Client domains, account names and private backup locations are omitted. The spam URLs remain as non-clickable text so readers can recognise matching indicators.

FindingWhat it meant for the repair
Desktop and mobile redirectsCheck timed redirects as well as device-dependent JavaScript
677 content recordsInspect revisions and custom post types, alongside live pages
Three infected carousel scriptsInspect linked assets after the database repair
Eight matching maintenance filesCompare contents and hashes before replacing files
Mobile retest passed according to the ownerReport the observed recovery separately from a full security clearance

The fake maintenance page visitors can mistake for a real outage

A maintenance message can look convincing while concealing an unauthorised change. The supplied screenshot below illustrates the dark maintenance screen associated with this investigation. It belongs to the case material; it is not a Ravensdale service-status notice.

Supplied dark maintenance-screen reference with the message We'll be back shortly, a Scheduled Maintenance badge and three status panels
Case-study reference: the supplied fake maintenance screen, not a Ravensdale status page. This design differs from the white WordPress-style output in the eight recovered files; the screenshot alone does not establish its generating code or redirect behaviour. Select the image to view it at full size.

The screenshot reads “We'll be back shortly.” and “Service Maintenance Center”. The eight matching files recovered later defined a different, white WordPress-style page. We cannot identify the screenshot's generating code or prove redirect behaviour from the image alone. The recovered files and their exact wording are documented in the fake maintenance file inspection below.

Recognising the urshort redirect

The original database payload pointed to:

https://urshort.com/GJRiGxMgA0r6

A later payload in the carousel scripts used:

https://ushort.dev/rltBnhFeJ0r2

Notice the spelling: urshort in the first domain and ushort in the second.

While preparing this article, a separate report was supplied describing a similar delayed redirect involving:

https://urshort.live/LkZRChpuD0r5

That third address is a reported example, not an indicator recovered from the repaired site. Similar names and behaviour do not establish a shared attacker or entry point.

If you are searching for urshort.com/GJRiGxMgA0r6, urshort.live/LkZRChpuD0r5, or part of either address, inspect the website's files and database. The matching text may be in settings, revisions, generated output or linked scripts rather than the visible page.

Different spam destinations were reported from different locations during our case. We did not establish the routing mechanism or attribute the destinations to a particular advertising network.

Why desktop and mobile behaved differently

The original injection combined a timed redirect with a device-dependent redirect. Its meta refresh contained:

<meta http-equiv="refresh"
      content="7; url=https://urshort.com/GJRiGxMgA0r6" />

The accompanying JavaScript checked navigator.userAgent for mobile device strings or used window.matchMedia("(pointer:coarse)").matches, then called location.replace().

The meta refresh explains the original all-device symptom. The JavaScript explains why some mobile visitors could leave sooner. The pointer query detects a type of input device; it does not require someone to tap the screen first.

Later, a separate mobile-dependent payload remained in external JavaScript after the database had been cleaned. That distinction matters: this was an all-device redirect problem with an additional mobile-dependent mechanism, not an incident that affected phones alone.

We confirmed those client-side checks. We did not confirm referrer filtering, cookies that suppressed repeat redirects, or special treatment of logged-in administrators.

First, preserve the evidence

Backups were taken before database edits and targeted file repairs. Suspect files were quarantined outside the public website, with their original paths recorded.

An administrator can run WP-CLI as the site account using a helper like this:

SITE_USER='siteaccount'
SITE_ROOT='/home/siteaccount/public_html'

wp_site() {
  runuser -u "$SITE_USER" -- wp --path="$SITE_ROOT" "$@"
}

Inspect configuration and loaders before commands that execute website PHP. --skip-plugins still allows must-use plugins to load. The core checksum command avoids loading WordPress; that does not make every other WP-CLI command equivalent. Core checksum documentation

1. Find the database changes

The WordPress home option had been changed to the spam URL. siteurl still contained the legitimate website address. Redirect code also appeared in 677 post_content records, including revisions and custom post types. That figure does not mean 677 live pages were affected.

For an already authorised database session, these read-only queries illustrate the checks. Replace wp_ with the installation's actual table prefix:

SELECT option_name, option_value
FROM wp_options
WHERE option_name IN ('home', 'siteurl');

SELECT COUNT(*) AS content_records_containing_urshort
FROM wp_posts
WHERE post_content LIKE '%urshort.com%';

SELECT ID, post_type, post_status, post_title,
       LEFT(post_content, 350) AS content_start
FROM wp_posts
WHERE post_content LIKE '%urshort.com%';

The repair script backed up the database, restored home and removed the exact identified meta-refresh-and-script payload from matching content. It did not strip every script from every post.

The recorded output included:

home_settings_corrected
1
content_records_cleaned
677
content_records_still_containing_urshort
0

Both homepage settings then contained the legitimate domain. The database backup was retained outside the public website.

Repair the remaining serialised SEO option

Another match remained inside Yoast's serialised wpseo option, under home_url. The unwanted address appeared in this token:

s:32:"https://urshort.com/GJRiGxMgA0r6";

PHP serialisation stores string byte lengths. Replacing the URL without correcting that length can invalidate the serialised value. Our repair corrected the specific key/value token while preserving its structure. The follow-up option check returned no remaining match for that address.

For readers choosing WP-CLI instead, it provides a serialisation-aware search-and-replace operation. This is an alternative dry-run example, not the script used for the original correction:

wp_site --skip-plugins --skip-themes search-replace \
  'https://urshort.com/GJRiGxMgA0r6' 'https://example.com' \
  wp_options --dry-run --report-changed-only

Use the actual options table and intended homepage URL. Review the result and preserve a backup before any write. The WP-CLI reference documents how serialised data is handled.

A URL replacement is appropriate only where a setting should contain that URL. Replacing a destination inside an injected script is not equivalent to removing the script: it could leave a self-redirect or timed reload.

2. Clear affected Divi output and rebuild the SEO index

Matching code appeared in generated Divi files under wp-content/et-cache. Affected generated cache output was cleared so it could be rebuilt from the corrected source content.

The canonical URL initially remained wrong because Yoast's stored index data also needed rebuilding. Once the settings had been corrected and the plugin could be loaded, the equivalent command was:

# Write operation: removes and rebuilds Yoast indexables.
wp_site yoast index --reindex

The command prompted for confirmation, then completed the indexing stages. This was a specific SEO repair, not a malware scan. Yoast documents the --reindex behaviour in its WP-CLI guide.

The terminal also produced PHP deprecation notices from cPanel's bundled WP-CLI dependencies. Their paths identified CLI compatibility warnings; the notices themselves did not demonstrate another infection.

3. Inspect cron, processes, MU plugins and uploads

The site's Unix crontab was empty, and a process snapshot listed no processes for the account at that moment:

crontab -u "$SITE_USER" -l
ps -u "$SITE_USER" -o pid,ppid,etime,args

find "$SITE_ROOT/wp-content/mu-plugins" -type f -ls

find "$SITE_ROOT/wp-content/uploads" -type f \
  \( -iname '*.php' -o -iname '*.phtml' -o -name '.user.ini' \
     -o -name '.htaccess' \) -print

Those results had limits. An empty Unix crontab does not inspect WordPress scheduled events or server-level jobs. A process snapshot cannot exclude earlier or intermittent activity.

The file listing revealed two deeply nested, unexpectedly named PHP trees under mu-plugins. Suspect upload files were also present at:

wp-content/uploads/2012/ajax30.php
wp-content/uploads/2014/02/wp-theme.php

The two MU subdirectories and those upload files were moved into a private quarantine outside the website. This is the anonymised form of those moves, run by the administrator after identifying the files:

# Write operations: move the identified items outside the public website.
QUARANTINE=$(mktemp -d /root/wordpress-quarantine-XXXXXXXX)
chmod 700 "$QUARANTINE"

mv -v -- "$SITE_ROOT/wp-content/mu-plugins/uctlemf" \
  "$QUARANTINE/mu-uctlemf"
mv -v -- "$SITE_ROOT/wp-content/mu-plugins/oflwxum" \
  "$QUARANTINE/mu-oflwxum"
mv -v -- "$SITE_ROOT/wp-content/uploads/2012/ajax30.php" \
  "$QUARANTINE/uploads-2012-ajax30.php"
mv -v -- "$SITE_ROOT/wp-content/uploads/2014/02/wp-theme.php" \
  "$QUARANTINE/uploads-2014-02-wp-theme.php"

These paths describe this case's findings. They are not a list of filenames that every affected site will contain.

We read mu-plugins/0-worker.php separately. Its captured contents were consistent with a ManageWP Worker loader, which included the main plugin only when active. That did not independently verify the underlying plugin or management account.

WordPress automatically loads top-level PHP files in the MU directory. Nested files need a loader or another execution route; being inside mu-plugins does not prove that every nested file executes on every request. Must-use plugin reference

The MU directory's .htaccess also contained permissive PHP access rules. It was another configuration item to review, rather than proof of a particular exploit.

4. Interpret the checksum warnings

Core and plugin checks helped identify changed and unexpected files:

wp_site core verify-checksums --include-root

wp_site --skip-plugins --skip-themes \
  plugin verify-checksums --all --strict

wp_site --skip-plugins --skip-themes \
  plugin list --fields=name,status,version --format=csv

wp_site --skip-plugins --skip-themes \
  theme list --fields=name,status,version --format=csv

WordPress's wp-admin and wp-includes directories had already been replaced earlier in the repair. This did not address files elsewhere in the installation or stored database content.

The core report flagged changed license.txt and readme.html files, plus extra files in a suspicious well-known tree. That directory had no leading dot, an important detail when comparing paths with .well-known.

It also flagged sitemaps, robots.txt, logs and other non-core items. “File should not exist” means outside the expected WordPress distribution, not automatically malicious. The recorded report did not show a core PHP checksum mismatch.

Plugin reports showed widespread JavaScript mismatches and added index.php files. Those required closer inspection. A generic index.php name alone is not grounds for deletion. Plugin checksum documentation

The carousel plugin was identified from its header as Divi Carousel by DiviGear, stored under dg-carousel. It was a premium component, requiring a vendor package rather than an ordinary WordPress.org download.

Finding injected code in a plugin file establishes that this copy was modified. It does not establish that a vulnerability in that plugin provided initial access.

5. Reinstall Divi and resolve the archive layout

The uploaded Divi ZIP passed its compressed-data test, but it stored style.css and the other theme files at the archive root. An initial attempt to read Divi/style.css returned:

caution: filename not matched: Divi/style.css

These read-only checks illustrate how we confirmed the structure:

DIVI_ZIP='/home/siteaccount/repair-staging/Divi-upload.zip'

unzip -t "$DIVI_ZIP"
unzip -Z -1 "$DIVI_ZIP" | sed -n '1,25p'
unzip -p "$DIVI_ZIP" style.css | sed -n '1,30p'

The theme header identified Divi 4.27.9. The archive was checked and repacked with the files under a Divi/ directory. The previous theme and original upload were backed up. The installation used WP-CLI's local-ZIP route, equivalent to:

# Write operation: install the reviewed, correctly repacked theme ZIP.
wp_site --skip-plugins --skip-themes theme install \
  '/home/siteaccount/repair-staging/Divi-repacked.zip' --force

Installation succeeded:

Theme updated successfully.
Success: Installed 1 of 1 themes.
DIVI_REINSTALLED

That version records what was installed during this repair. It is not a recommendation to use an older release when a newer compatible vendor package is available.

The reinstall addressed the theme. It did not repair separate plugin assets.

6. Follow the JavaScript when mobile redirects persist

Mobile testing still showed redirects after the earlier repairs. Inspecting the scripts actually loaded by the page exposed the remaining payload.

Three files contained an injected prefix:

wp-content/plugins/dg-carousel/scripts/builder-bundle.min.js
wp-content/plugins/dg-carousel/scripts/frontend-bundle.min.js
wp-content/plugins/dg-carousel/scripts/swiper.min.js

The prefix checked mobile user-agent strings or (pointer:coarse), then used location.replace() to navigate to the ushort.dev URL listed earlier.

The targeted cleanup backed up each original and removed only the exact matched prefix, preserving ownership and permissions. It reported:

CLEANED: scripts/builder-bundle.min.js
CLEANED: scripts/frontend-bundle.min.js
CLEANED: scripts/swiper.min.js
FILES CLEANED: 3

This corrected an important gap in our earlier verification: clean-looking HTML did not mean that every linked asset was clean. A full comparison of the remaining plugin against a trusted vendor package was a separate integrity question.

A response check must include the body and linked assets

The following is an anonymised diagnostic example of fetching HTML for inspection. Run it from a working directory outside the webroot:

SITE_URL='https://example.com/'
MOBILE_UA='Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1'

curl --silent --show-error --max-time 30 \
  -A "$MOBILE_UA" -H 'Cache-Control: no-cache' \
  --dump-header response-headers.txt \
  --output page.html "$SITE_URL"

grep -nEi -- \
  'urshort|ushort|http-equiv|location[.](replace|href)|pointer:coarse' \
  page.html

curl does not execute JavaScript or follow an HTML meta refresh. A 200 OK response cannot establish that a browser will remain on the site. A headers-only curl -I check would miss a payload embedded in the response body.

Inspect linked scripts as text too. A user-agent header is useful for comparing responses, but it does not reproduce a real phone's JavaScript environment. In our case, actual mobile testing was essential.

7. Search the wider installation for remaining indicators

After cleaning the three scripts, this search located additional matches:

grep -rIlE -- \
  'ushort[.]dev|urshort[.]com|rltBnhFeJ0r2|GJRiGxMgA0r6' \
  "$SITE_ROOT"

The results included a staging plugin directory, an unexpected Akismet3 directory and several index.php files elsewhere under wp-content. The staging and Akismet3 directories were quarantined outside the website.

Their names were not sufficient evidence by themselves. The matching contents were the reason to investigate them. Likewise, a plugin absent from the active inventory can still leave files on disk, and a direct PHP request may not depend on normal plugin activation.

For someone investigating the separately reported urshort.live address, this additional search example broadens the indicators. It is not the search used to produce our final case result:

grep -rIlE -- \
  'urshort[.](com|live)|ushort[.]dev|GJRiGxMgA0r6|rltBnhFeJ0r2|LkZRChpuD0r5' \
  "$SITE_ROOT"

Results are candidates for inspection, not an automatic deletion list. A string search can miss encoded or differently written payloads.

8. Inspect the fake maintenance files

Eight remaining PHP files were identical. They displayed a WordPress-style maintenance page with a white panel, a WordPress heading and a loading animation.

The exact visible wording was:

Briefly unavailable for scheduled maintenance.

Check back in a some hours.

The unusual second sentence is reproduced exactly because someone searching “WordPress Check back in a some hours” may be trying to identify the same sample.

The file excerpt also showed these headers:

http_response_code(503);
header("Retry-After: 3600");
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Pragma: no-cache");
header("Expires: 0");

They made a request to the file resemble a temporary outage. The captured excerpt continued into JavaScript after the page markup, and the files matched the known redirect indicators. The complete script tail was not in that excerpt, so we do not attribute additional capabilities to it.

Locations and fingerprint

These are the eight paths relative to wp-content:

managewp/index.php
plugins/dg-carousel/functions/index.php
plugins/dg-carousel/includes/index.php
plugins/dg-carousel/languages/index.php
plugins/dg-carousel/styles/index.php
plugins/index.php
themes_backup/index.php
wflogs/index.php

All eight files were 2,018 bytes, with the same SHA-256 hash:

18720ed37d93a3af4453e5fe029cee8489309ce818cf5eaeb858ff733c041771

These read-only examples can help locate candidates and compare the exact sample:

find "$SITE_ROOT/wp-content" -type f \
  -name 'index.php' -size 2018c -print

grep -rIlF -- 'Check back in a some hours' \
  "$SITE_ROOT/wp-content"

sha256sum "$SITE_ROOT/wp-content/plugins/index.php"

Filename, size and wording are clues. The hash identifies this exact byte-for-byte sample; variants may differ.

Our cleanup checked all eight against the expected hash before backing them up and replacing only those matches with minimal no-output placeholders. It reported:

FILES REPLACED: 8

It did not replace legitimate application entry points or delete every index.php file. We did not establish that these eight files provided command-execution access or represented the initial entry point.

The final search and retest

After those replacements, the known-indicator filesystem search returned no matches. The site owner then reported that mobile browsing no longer redirected.

AreaRecorded result
Homepage settingsOne altered home value corrected; siteurl already legitimate
ContentExact identified payload removed from 677 records
Original shortener in checked contentZero remaining matches
SEORemaining option corrected; indexables rebuilt
DiviTheme reinstalled successfully after correcting ZIP layout
CarouselIdentified redirect prefix removed from three scripts
Suspect filesTwo MU trees, two upload PHP files and two further plugin directories quarantined
Maintenance copiesEight identical files backed up and replaced
Final filesystem searchNo matches for the original known redirect indicators
Mobile retestSite owner reported that redirects had stopped

The symptom was resolved in that retest. This record does not establish a complete clearance of every package, account, scheduled task or server setting. The final search covered the observed urshort.com and ushort.dev indicators, not the later urshort.live report.

Could PHP2Shell have caused this?

It is a possible entry route, but it was not confirmed here. PHP's October 2026 advisory describes CVE-2026-109618 and CVE-2026-109619, commonly called PHP2Shell, as vulnerabilities that can allow unauthenticated remote code execution. Updating WordPress does not replace the PHP runtime patch. Official PHP advisory

Attribution would require evidence about the domain's actual web PHP runtime, patch history, worker restarts and relevant request, access and error logs. File modification dates alone are insufficient because they can be altered.

Compromised credentials, vulnerable components, remote-management access and an existing foothold would also need investigation. Finding the redirect mechanism did not establish how the attacker first obtained write access.

For runtime patching, see our related guide: Critical PHP RCE Vulnerabilities: How to Update Safely.

What remains after a redirect cleanup?

Further account, credential, scheduler and configuration review is distinct from removing the visible payload. Those checks should not be described as completed without their results.

The follow-up questions are practical: who had administrator and hosting access, which credentials or sessions need revoking, whether scheduled jobs can restore files, and whether remaining code matches trusted packages. Preserve logs and quarantine samples to support that investigation. WordPress's hacked-site guidance also covers documenting the incident and reviewing access.

We did not establish whether customer data had been accessed or copied. The redirect confirms unauthorised behaviour; it does not settle that separate question.

Questions this case helps answer

Why does WordPress redirect after a few seconds?

In this case, a meta refresh specified a seven-second delay. Check the response body and loaded assets before attributing a similar symptom elsewhere to the same payload.

Why did mobile redirects remain after the homepage was fixed?

Separate JavaScript files still contained a mobile-dependent redirect. The database repair did not remove those filesystem changes.

Is “Briefly unavailable for scheduled maintenance” always malware?

No. WordPress can legitimately enter maintenance mode during an update. Here, repeated files and matching redirect indicators established the problem. Deleting a .maintenance file would not have removed those copies.

Does a clean checksum report prove the entire website is clean?

No. It covers the files and package versions actually verified. Database content, extra directories, credentials and server configuration require their own assessment.

Need help with a WordPress redirect?

If your website redirects to a short link, an unexpected maintenance page or unrelated spam, Ravensdale Digital can help investigate the behaviour and plan the repair. Our WordPress development and website maintenance services cover technical troubleshooting and ongoing support.

Contact us about a WordPress redirect with the affected page, destination URL, device and timing. In this case, following that evidence beyond the homepage led us to database injections, external scripts and the remaining maintenance files.

Is your WordPress website redirecting visitors?

Send the affected URL, destination, device and approximate delay. Ravensdale Digital Services can investigate the redirect, preserve evidence and plan a targeted cleanup with desktop and mobile checks.

Related Posts

More Articles

WordPress versus Next.js comparison dashboard for business website planning
Web Development

WordPress vs Next.js: Which Is Better for Your Business Website?

WordPress is still a strong choice for content-managed business websites, while Next.js is better suited to custom, performance-focused, app-like, or integration-heavy builds. The right choice depends on how your website will be edited, maintained, marketed, and extended.

Updated 5 May • 12 min read

Read Article