When the cleanup went wrong

Your scanner flagged a file. Work out whether it is right.

A wrong verdict costs you a working site, and a verdict you ignore costs you the whole site. Neither is a coin toss. Three checks settle almost every flagged file in about five minutes, and none of them need you to read PHP.

Checked against a live WordPress install on .

Why clean files get flagged

A scanner that reads file contents is looking for the shapes malware takes. Long base64 strings. eval on something assembled at runtime. A variable function name. Characters escaped so the source reads as noise. Those shapes describe malware accurately, and they also describe a great deal of software that is doing nothing wrong.

Four kinds of legitimate file trip signature scanners over and over.

  • Minified and bundled JavaScript or PHP. A build step strips the newlines and shortens the names. The output is unreadable by design, and unreadable is the property the scanner is measuring.
  • Licence checks in paid plugins and themes. Vendors obfuscate the part that talks to their licence server so it is harder to patch out. That code phones home from an encoded string, which is also what a backdoor does.
  • Caching layers that write PHP. Page caches, object caches and some page builders generate PHP files at runtime and rewrite them constantly. A file that appeared an hour ago, contains generated code and did not exist at install time looks exactly like a dropper.
  • Anything with no published copy. Custom themes, bespoke plugins and code your developer wrote have nothing to compare against, so a scanner has only the contents to go on. This is where wrong calls concentrate.

There is a second family of wrong calls that has nothing to do with the file's contents. An integrity check compares your files against the published copy of the exact version you are running. When the directory has not yet published checksums for a release, every file in that component comes back unrecognised in the same instant. Our own 1.0.1 release did this for about nine hours. If a whole plugin or theme lights up at once and the site is behaving normally, that is the first thing to suspect.

The three checks that settle it

Run them in this order. The first one answers most files on its own, and each is faster than the one after it.

1. Does the file match the published copy?

Find out which plugin, theme or part of core the file belongs to, and what version you have installed. Download that exact version from where it came from. Compare the file in the archive against the file on your server, byte for byte.

Identical means the file is the original and the detection is wrong about it. Different means read the difference, which is check three. The full procedure, including where to get each of the three file classes, is in restore modified core, plugin and theme files.

Match the version number exactly. A file that differs from a neighbouring release differs for an honest reason and tells you nothing.

2. When was it last modified?

List the directory sorted by modification time. A legitimate plugin file carries roughly the same timestamp as the rest of its plugin, because they were all written to disk during the same install or update. An injected file is the odd one out, usually by months.

Two cautions. A timestamp is trivially forgeable and attackers who bother do forge it, so a normal-looking date is weak evidence in favour and an odd one is strong evidence against. And a caching layer legitimately rewrites its own files every few minutes, so check what directory you are looking at before you read anything into a fresh date.

3. Does the flagged code run?

A file only matters if something loads it. Search your site's code for the file's name, and for the function or class it defines. A PHP file nothing includes, in a directory nothing routes to, is inert whatever it contains. A file the theme loads on every request is not.

This check does not tell you the file is clean. It tells you how much time you have. Nothing loads it means you can take an hour to decide. Your header loads it means decide now.

Reading the flagged code

Open the file in a text editor. You are looking for a shape, not for meaning, so you do not need to know PHP to do this.

An injection is a guest in someone else's file. It sits at the very top, before the opening comment, or at the very bottom after the last closing brace. It is often one enormous line in a file where every other line is a reasonable length. The code around it is formatted and indented consistently and the injected block is not, because a person wrote one and a script wrote the other. Delete it and the file still makes sense as a file.

A packed but honest library is uniform. The whole file is dense. There is no readable part it was bolted onto, because there is no host: the file was generated in one piece. It usually carries a licence header or a source-map comment, and the same file appears in the vendor's published archive, which is check one settling it.

Read what the suspicious block reaches for. Fetching a remote address and running the response, writing a new PHP file, reading a cookie or a request parameter and executing it, or sending a request to a domain you do not recognise are all things a backdoor does and a licence checker does not. A licence checker calls one documented endpoint belonging to the vendor whose plugin you are reading.

Do not run the code to see what it does. Reading it is enough, and running it is how a decision becomes an incident.

When you still cannot tell

Some files do not resolve. Custom code with no upstream copy, written by someone you can no longer ask, is the common case. Isolate it instead of deciding.

  1. Copy the file somewhere outside the web root. Whatever you do next, you can undo it.
  2. Rename it in place rather than deleting it. Add a suffix such as .quarantine so nothing loads it and the bytes stay where you can find them. This is the same isolation a delete gives you and it costs nothing to reverse.
  3. Load the site. The front page, the admin, and the feature you think the file belongs to. A fatal error naming the file tells you something real depended on it, which is evidence about what the file was, not about whether it is clean.
  4. Leave it renamed for a week. If nothing breaks and nothing complains, the file was not doing a job anybody wanted done.
  5. Ask somewhere the file can be seen. The plugin or theme vendor can tell you in a sentence whether a file belongs to them. For core, the file is published and check one already answered it.

If you renamed the file and the site broke, put it back and read recovering files a security plugin removed. A file the site needs is a file you have to clean rather than remove.

Living with a file that keeps getting flagged

Once you have decided a file is fine, tell the scanner so. Every plugin has some form of this and the choice you make matters more than it looks.

Silence the file's current contents, not its path. An exclusion tied to the file's hash lapses the moment anything edits that file, so the day an attacker does inject something, the file is judged again. An exclusion tied to the path holds forever, including through the change you did not make. Most scanners offer both, labelled along the lines of ignore until the file changes and ignore always. Use the first unless the path is rewritten constantly by design.

Exclude a directory only when you understand why it trips. Cache directories are the honest case: they generate PHP, they rewrite it constantly and no scanner will ever settle them. A plugin directory is not the honest case, and excluding one because it is noisy removes the only thing watching it.

Tell the vendor whose scanner got it wrong. A wrong call on a widely installed plugin affects everyone running it, and it usually gets fixed once someone reports it with enough detail to reproduce. Include the full path, the plugin or theme and its exact version, and the SHA-256 of the file. Wordfence, and every other signature-based scanner, fixes these when they are reported with that much.

Segurium has no report button in the plugin today. Use the plugin's support forum on WordPress.org, or the Support tab inside the plugin, with the same three details. Reporting a file does not change the verdict on your own site, so silence it locally as well.

Questions

Does Segurium produce false positives?
Yes. Every scanner that reads code does. Most of our verdicts come from a hash lookup, which either matches a known file or it does not, so a file that ships unmodified from WordPress.org is settled without anything reading it. The judgement happens on files nobody has seen before, and judgement is where a scanner is wrong. We publish no detection-rate figure because we have no benchmark to back one.
What does the ignore option actually do?
There are two and they behave differently. Ignoring until the file is the same is scoped to the file's current hash, so the moment anything edits that file the ignore lapses and the file is judged again. Ignoring always is scoped to the path, so it holds through every future change to that file. Use the first one by default. Reach for the second only on a path that is rewritten constantly, such as a cache directory.
Is a file safe because the integrity check says nothing about it?
No, and this is the trap worth knowing. The integrity check compares files against the published copy of that exact version. A premium plugin, a custom theme, or a child theme has no published copy, so those components are listed and never compared. Silence there means nobody could check, not that the check passed.
The scanner flagged every file in one plugin at once. What happened?
Almost always the version you have installed is newer than the checksums the directory has published. Every file in the component then comes back unrecognised at the same moment. It happened to our own 1.0.1 release, which read as hundreds of issues for about nine hours until WordPress.org caught up. Wait a day and re-scan before you act on a whole component lighting up at once.
Should I just delete the file to be safe?
No. Deleting is the one action on the list you cannot undo without a copy of the file from somewhere else, and half the files a scanner flags in a wrong call are legitimate plugin code your site still calls. Rename it, load the site, and see what breaks. That gives you the same isolation and takes the same ten seconds.
How do I report a false positive?
The plugin has no report button today. Use the WordPress.org support forum for the plugin, or the Support tab inside the plugin, and include the file path, the version of the plugin or theme it belongs to, and the SHA-256 of the file. Silencing the finding on your own site is a separate step and you should do it either way, because a report does not change the verdict on your install.

Next

Where the guessing goes away

Most of this page exists because a scanner that reads code has to guess. Segurium guesses less often by not reading most files at all: every file is fingerprinted and the fingerprint is looked up first, and around 94% of files on a site are settled that way without their contents being examined by anything. What is left is the code nobody has published, which is exactly the code that needs judging, and we get some of those wrong too.

Where it does differ is in what happens after. A file with injected code gets the injected code removed and keeps working, so a wrong call costs you a file you can restore in one click rather than a file you have to go and find.