Your site looks fine in a browser and Google shows Japanese titles for URLs on your domain. That gap is the infection working as designed. Four separate things are keeping it alive and only two of them are files.
Checked against a live WordPress install on
.
What Google is showing you
Most people meet this hack through a notification rather than through
their own site. A Search Console message about new pages you did not
publish, or a customer forwarding a screenshot of your brand sitting next
to Japanese text and a counterfeit handbag.
Search site:example.com with your own domain and you see
URLs you have never created, carrying Japanese titles and descriptions.
Click one and you get your ordinary 404 page, or your homepage, or
nothing unusual at all. Load your site normally and everything works.
Nothing was translated. Your pages are exactly as you left them. The
attacker generated new URLs on your domain, filled them with machine-written
Japanese, and monetised them with affiliate links to stores
selling fake brand merchandise. That is Google's own description of what
the campaign sells.
The two URL shapes
Google's documentation gives the older shape: pages in randomly named
directories, like http://example.com/ltjmnjp/341.html. A
directory name nobody would type, and a numbered HTML file under it.
The shape Sucuri documented in January 2025 is quieter and harder to
find. The spam URLs were plain query strings on the domain root,
example.com/?m=123456789, with the number changing per page.
There is a reason that works. m is the first entry in
WordPress core's list of public query variables, in
wp-includes/class-wp.php, where it means a date archive. So
every one of those URLs reaches WordPress, returns 200, and corresponds
to no file anywhere on your server. Searching the disk for them finds
nothing, because there is nothing to find.
What Search Console calls it
Google's Security Issues report has four hacked categories. This family
normally lands as Hacked: URL injection, which Google defines as a
hacker creating new pages on your site containing spammy words or links.
Where the attacker also edited your existing pages, you get Hacked:
Content injection alongside it.
A manual action or a security issue is a label on your whole property. It
does not tell you which file did it, and clearing it is the last step of
this page rather than the first.
Confirming the cloak
Before touching a file, prove that the site serves different content to
Google than it serves to you. Four checks, cheapest first.
1. Ask Google what it has
Run site:example.com on your own domain and page through the
results. Add a term to narrow it, such as a directory name you spotted or
a word in the spam titles. Write down a handful of the spam URLs. You
will need that list twice more before you are finished.
Search Console gives you the same thing in bulk. The Pages report and the
Performance report both list URLs Google knows about, and both export.
Take the export.
2. Inspect one spam URL as Google
This is the check that settles it. In Search Console, paste one of the
spam URLs into the inspection box at the top, then choose Test live
URL. When it finishes, open View tested page and read the
HTML tab.
That HTML is what Google's crawler received from your server a moment
ago. If it is full of Japanese text while your browser shows a 404 on the
same URL, the cloaking is confirmed and you know the payload runs on
request rather than sitting in a static file. The Screenshot tab
shows the same page rendered.
Older guides tell you to use Fetch as Google. That tool was retired with
the previous Search Console. The URL Inspection tool replaced it, and the
crawler it sends is named Google-InspectionTool.
3. Request the page as a crawler yourself
From a shell, ask for the URL with a Googlebot user agent and compare it
against an ordinary request.
Two different bodies means cloaking. Two identical bodies means very
little, and this is where people stop too early. Plenty of these payloads
check the source address as well as the user agent, and Google publishes
the crawler ranges as a JSON file that anyone can fetch. A request from
your laptop with a borrowed user agent fails that check and gets the
clean page. Treat a clean answer here as inconclusive and go back to the
live test in Search Console, which comes from a real Google address.
4. Read the property itself
Two screens in Search Console carry evidence that no file on your server
can show you.
The Sitemaps report. It lists only sitemaps submitted through
that report or through the API, which is Google's own wording. So an
entry there that you did not submit was submitted by somebody with
owner access to your property. Note the exact filename.
Settings, then Users and permissions. Look at the verified
owners. An address you do not recognise, often a throwaway Gmail
account, is part of the infection and not a coincidence.
The same cloaking mechanism drives the pharmacy variant of this campaign,
and the confirmation steps there are worth reading alongside these if the
injected text is English rather than Japanese:
pharmacy spam in your pages and search results.
The four places it lives
This is the part that makes the infection survive an ordinary cleanup.
People remove the PHP file, watch the spam keep appearing, and conclude
the cleanup failed. It did not. Three other layers were still running.
Layer
What it is
What removes it
Files under wp-content
The generator that builds a spam page per request, plus a
backdoor that rewrites it after you delete it
A file manager, FTP or SSH
Rows in wp_posts and wp_options
Spam posts published under your own permalinks, and a loader
row that runs on every page load
The WordPress admin or the database
A rewrite rule in .htaccess
Routes whole URL patterns into that one PHP file, and can
manufacture a Search Console verification file on demand
A text editor
A verified owner in Search Console
An account inside Google that can submit sitemaps and change
settings for your property
Google's console. Nothing on your server reaches it
The files
The generator is a single PHP file, usually somewhere nothing should be
executing PHP at all. Paths Sucuri has published from real cases include
wp-content/uploads/cache/spam.php and
news/index.php, with the working files beside them in
wp-content/uploads/.spam/ and
wp-content/cache/sitemap.xml. Google's documentation names
index.php, wp-load.php, 404.php
and view.php as the files most often injected rather than
added.
One hiding place deserves its own paragraph.
wp-content/mu-plugins/ holds must-use plugins, which load on
every request, have no activation record, and cannot be deactivated from
the admin. Sucuri documented a payload there as
wp-content/mu-plugins/.tott.log. Read that filename again. A
leading dot hides it from a directory listing and from most file
managers, and a .log extension keeps it out of every search
for *.php you are about to run.
The sitemap that is not yours
WordPress core does not write a sitemap file. It registers a rewrite rule
for wp-sitemap.xml and builds the XML from the database on
request. Yoast and Rank Math work the same way for their own sitemap
URLs. So a physical file called sitemap.xml,
sitemap_index.xml or something less obvious sitting in your
web root did not come from WordPress and did not come from your SEO
plugin.
Sucuri's January 2025 case is the clearest published example. A file
named spamurl.txt sat in the site root, listing spam URLs in
sitemap form, and it had been registered as a sitemap inside the site's
own Search Console property. Google crawled over 3,000 spam URLs from it.
The site's files had already been cleaned once. The spam kept indexing,
because the list feeding Google was a plain text file nobody had thought
to look at and a Search Console entry nobody had opened.
The rewrite rule that regrows a verification token
Google's own hacked-spam documentation publishes this marker, and it is
worth understanding rather than just deleting.
apache
RewriteEngine On
RewriteRule ^google(.*)\.html$ dir/file.php?google=$1 [L]
Search Console's HTML file verification method works by you uploading a
file named after a token Google gives you, and Google fetching it from
your web root. That rule makes any request for
google<anything>.html return a valid verification file
generated on the fly. The attacker can therefore verify ownership of your
property whenever they like, with no file for you to find. Google's test
for whether you have removed it is direct: request
example.com/google followed by a random string and
.html, and a 404 means the rule is gone.
Read every .htaccess on the site, not only the one in the
root, and treat any rule that routes a whole URL pattern into a single
PHP file as hostile.
The parts that are not files at all
Injected posts sit in wp_posts as ordinary published
content, which is why they get permalinks and appear in your own sitemap.
The loader that runs the payload is often a row in
wp_options, autoloaded on every request. Neither is visible
to anything that walks your filesystem.
Alongside them there is usually an administrator account you did not
create, which is how the attacker gets back in after you clean the files
and which is often how they verified themselves in Search Console.
Finding those, including the ones the user list does not show you, is
a procedure of its own.
Taking it out by hand
Work through this in order. Every command in this section was run
against WordPress 7.0.4 with WP-CLI 2.12.0, PHP 8.2, MySQL 8.0 and
Apache on 18 August 2026, and the output is quoted as it appeared.
Save the spam URL list first. Export it from Search Console
before you change anything. Once the generator is gone the URLs stop
resolving and you lose the ability to enumerate them, and you need the
list to check your work later.
List what changed recently. Run this from your WordPress root
and widen the window until the results stop being interesting.
bash
Expect legitimate hits. Minified libraries and licence checks in paid
plugins use the same functions. If you are unsure whether a hit is real,
three checks settle most flagged files.
Search for the cloak itself. The payload has to recognise a
crawler, so it names one.
bash
This grep is noisy and you should know how noisy before you run it. On
a clean install with nine plugins it returned nine files, every one of
them legitimate: Akismet, a file manager, a bundled logging library and
some coding-standard fixtures. Open each hit and read the code around
the match. Deleting on the strength of a grep is how people remove
Akismet.
Read the injected code without running it. Decode a base64 block
in a text editor or with a decoder that only prints. Do not execute the
file to see what it does, and do not load it in a browser. Reading is
enough to identify it, and running it is how a cleanup becomes a second
incident.
Clean every .htaccess.bash
find . -name ".htaccess"
Delete any rule routing a URL pattern into a single PHP file, and any
rule matching ^google(.*)\.html$. Keep the block between
# BEGIN WordPress and # END WordPress, which
WordPress manages itself.
Delete the rogue sitemap file. List your web root and remove any
sitemap or plain text file of URLs that WordPress and your SEO plugin
did not create. Note the exact filename before deleting it, because you
need it in Search Console in a moment.
bash
Put the modified files back. Injections into core, plugin and
theme files are repaired by restoring the published copy of that exact
version, which is
its own procedure with three different sources.
Restoring core does not remove files the attacker added, so the earlier
steps are not optional.
Find the injected posts. This is database work. Two queries
find the same thing and the second avoids the regular-expression
engine.
sql
SELECT ID, post_title, post_type, post_status, post_date
FROM wp_posts
WHERE post_title REGEXP "[^ -~]";
SELECT ID, post_title, post_type, post_status, post_date
FROM wp_posts
WHERE LENGTH(post_title) <> CHAR_LENGTH(post_title);
Both ask the same question: does this title contain a character outside
plain ASCII. On MySQL 8.0, "日本語ページ" REGEXP "[^ -~]"
returns 1 and "Hello world!" REGEXP "[^ -~]" returns 0.
The second query works because a multibyte character counts as several
bytes and one character, so LENGTH and
CHAR_LENGTH disagree only when the title is not pure
ASCII.
Read the results before deleting anything. Both queries also
match legitimate titles containing an accent, a curly quote or an emoji.
Run them through WP-CLI, which handles the connection for you.
bash
wp db prefix
wp db query "SELECT ID, post_title, post_type, post_status, post_date FROM wp_posts WHERE LENGTH(post_title) <> CHAR_LENGTH(post_title);"
wp db prefix printed wp_ on the install used
here, and yours may differ. Read $table_prefix in
wp-config.php and substitute it into the queries. A query
that matches nothing prints nothing at all and exits 0, so silence is a
result.
Delete the confirmed spam posts. The admin works fine for a
handful. For hundreds, delete by ID and skip the trash.
bash
$ wp post delete 12 --force
Success: Deleted post 12.
Without --force the post moves to the trash, keeps its row
and stays reachable by ID.
Remove the administrator you did not create, and reassign their
content rather than deleting it. Do that
carefully, because
deleting a user with content attached can take posts with it.
When the files and the rows are gone, load one of the spam URLs from your
saved list. You should get your own 404 page from every request, whatever
user agent you send.
Search Console and the index
Everything in this section happens inside Google's console. None of it is
on your server, none of it is affected by anything you deleted, and
skipping it is why cleaned sites keep indexing spam.
Remove the owner, and remove their tokens first
Open Settings, then Users and permissions. Next to the
owner you do not recognise, open Verification details, which lists
the tokens that account used to prove ownership. Delete every one of
them, then remove the owner. The order matters: a token you leave in
place is a route straight back to owner status.
A token can be any of the methods Google supports, so check all of them
against your own site.
An HTML file in your web root, named after the token. Remember the
.htaccess rule from earlier: the file may not exist on
disk at all and still be served.
A <meta name="google-site-verification" content="..." />
tag in your homepage head. Read the page source and check every one you
find against your own account.
A DNS TXT or CNAME record at your registrar, which survives anything
you do to the site.
Google Analytics or Google Tag Manager access, which verifies ownership
without touching your server at all.
Google's warning on this is worth quoting: "If you have been hacked, this
is only a temporary solution. Even if you remove the token, a hacker can
probably add it back." The next section is what stops that.
Remove the sitemap, and understand what that does not do
In the Sitemaps report, click the sitemap you did not submit, open the
more options button, and choose Remove sitemap. Then read what
Google says happens next: "Deleting a sitemap removes the sitemap from
this report, but Google won't forget the sitemap or any URLs listed in
it."
So the removal tidies the report and changes nothing about the index. The
URLs leave the index when Google recrawls them and finds them gone, which
is why deleting the generator and the posts had to come first. Once those
URLs return 404 or 410, the index drains on its own over weeks.
Check your work the way Google will
Take three or four URLs from the list you saved and run Test live
URL on each. The HTML that comes back is what Google's crawler gets
right now. A 404 there means the URL is genuinely dead rather than dead
only for you.
If you need the spam out of results today
The Removals tool hides a URL or a prefix from Google results quickly.
Google is clear about the limit: a successful request lasts about six
months, and it hides the result rather than removing the page. Use it to
keep counterfeit ads off your brand name while the recrawl catches up,
and do not treat it as the fix.
Request the review
Once the site is clean, open the Security Issues report and choose
Request Review. Write what you found and what you did, per
category. A line like "for the URL injection, I removed an injected PHP
file under wp-content, deleted 340 spam posts, removed a rogue Search
Console owner and updated the vulnerable plugin" is the kind of detail
that gets reviewed rather than bounced.
Two things Google says about the review, and both catch people out. It
takes several days to a few weeks. And fixing the issue on some pages
earns no partial return to search results, so a half-cleaned site fails
the review and puts you back in the queue. Do not resubmit while a review
is pending.
Closing the way back in
This infection has two ways back. The hole that let the attacker in, and
the Search Console ownership that lets them keep pointing Google at
whatever they generate next. Close both or you do this again.
Find how they got in
The generator file is the payload. Something wrote it, and that something
is still there unless you found it. An administrator account you did not
create, an outdated plugin with a public exploit, a stolen password, or a
hosting account shared with another compromised site are the usual
answers. Working backwards from the timestamps to the entry point is
the whole subject of a separate guide,
and it is worth reading if this is the second time you have cleaned this
site.
One documented route deserves naming, because it explains the Search
Console owner without involving your Google password. CVE-2020-8934 in
Site Kit by Google let any authenticated user, at any role, read a setup
URL out of the admin page source and complete Google's owner verification
with it. A subscriber account was enough to become an owner of the
property. It was fixed in Site Kit 1.8.0, released on 7 May 2020, so the
bug itself is long patched. It is here as the clearest published example
of how a low-privilege WordPress account turns into control of your
Search Console property.
Rotate what the attacker saw
Every administrator password, and force a logout so existing
sessions die.
The salts in wp-config.php. Changing them
invalidates every login cookie on the site at once.
The database user's password, updated in
wp-config.php at the same time. Sucuri puts this in their
standard cleanup for good reason: the credentials were sitting in a
file the attacker could read.
Any API key or token stored in the database, including the ones
your SEO and mail plugins hold.
Take away the places it likes
Stop the uploads directory executing PHP. On Apache, a
.htaccess in wp-content/uploads/ does it:
apache
<Files "*.php">
Require all denied
</Files>
Check that nothing legitimate on your site relies on running PHP from
uploads before you deploy that, then confirm it by requesting a known PHP
file under uploads and expecting a 403.
Then keep an eye on the directory nobody watches. Must-use plugins load
with no activation record and no way to switch them off from the admin,
so a file dropped there runs until somebody looks at the folder.
bash
wp plugin list --status=must-use --fields=name,title
wp plugin list --status=dropin --fields=name,title
Audit ownership the way you audit users
Add the Users and permissions screen to whatever routine you already
have for checking your WordPress administrator list. The two lists get
attacked together, only one of them is on your server, and the one that
is not is the one that survives a full site restore.
Questions
My site looks completely normal. Why does Google show Japanese?
Because the infected pages are served to crawlers and not to you. The injected code reads the request, decides whether it came from a search engine, and returns spam to the crawler and your ordinary page or a 404 to everyone else. Google calls this cloaking. Your browser is the one visitor guaranteed never to see the hack, which is why the site can sit infected for months.
Did somebody translate my pages into Japanese?
No. Your own pages are untouched. The attacker created new URLs on your domain, filled them with generated Japanese text, and pointed Google at them. Google's Security Issues report files this as Hacked: URL injection, which it describes as a hacker creating new pages on your site.
I deleted the malicious files and the Japanese results are still there.
Two different things keep them alive. Google will not drop a URL from the index until it recrawls and finds nothing, which takes weeks for thousands of pages. And if the attacker registered a sitemap in your Search Console property, removing that sitemap from the report does not make Google forget the URLs in it. Google says so in its own documentation. The URLs have to start returning 404 or 410 on their own.
There is a verified owner in my Search Console that I do not recognise.
That is part of the same infection, and it is the part that lives inside Google rather than on your server. An owner can submit sitemaps, change geotargeting and request removals for your property. Removing them takes two steps: delete every verification token they hold, then unverify the account. If you leave one token in place, they re-verify themselves on it.
Do I have to remove the injected posts myself?
Yes. Injected posts are rows in wp_posts, and the loader that publishes them is often an autoloaded row in wp_options. Take the generator and the backdoor off disk first, or the posts come back on the next page load. Then use the two queries in this guide to list the posts, read what they return, and delete the confirmed ones with wp post delete --force.
How long before the Japanese results disappear from Google?
Weeks, sometimes longer. After the URLs return 404 or 410, Google drops them as it recrawls. A security review, requested from the Security Issues report, takes several days to a few weeks and clears the warning label rather than the URLs. If you need the spam gone from results today, the Removals tool hides a URL for about six months while the real fix propagates.
What the file half of this looks like without the manual work
The file half of that procedure is one job repeated: fingerprint every
file, work out which ones are not what they should be, and take the
injected code out without breaking the file it was hiding in. Segurium
runs that job for you. It walks the filesystem, checks each file against
what that exact version should contain, and where a legitimate file has
injected code in it, writes the cured bytes back so the file keeps
working rather than handing you a deletion to recover from.
Then work the rest of it. Re-run the two post queries after the file
cleanup, look for the autoloaded row in wp_options that was
publishing them, confirm the administrator list holds only accounts you
recognise, and go back through Search Console for verification tokens and
sitemaps you did not submit. That last one sits inside Google, and it is
where this infection keeps its foothold once your files are clean.