How to Analyze a Hacked Website

Invasion Investigation · Part 6

Findings are not conclusions

You finished the last post with a pile of facts: files that don’t belong, dates, accounts, settings that changed, things you can’t explain.

None of that is an answer. Gathering and deciding are different skills, and the second one is where investigations go wrong — not by missing evidence, but by reading it confidently and wrongly.

This post is about not fooling yourself.

Label every claim

Before anything else, adopt a habit that costs nothing and changes everything. Every conclusion you reach gets one of three labels:

ESTABLISHED — proven by evidence you can point at. Two independent sources agree, or the artifact speaks for itself.

INFERRED — strongly supported, not proven. It’s the best explanation of what survived, and you’re going to act on it, but you can’t demonstrate it.

UNKNOWN — you don’t know, and saying so is a finding rather than a failure.

The point isn’t modesty. It’s that an inference you’ve labelled is safe to act on, while an inference that has quietly promoted itself to a fact will eventually collapse and take everything built on top of it with it.

From the case file. The analysis document for this compromise carries a label on every claim. The entry point is still marked inferred after eight months of work — and the response was the same as if it had been proven. Labelling it didn’t weaken the action. It just kept the record honest.

Build the timeline first

Everything else depends on sequence, so sequence comes first.

Take the clusters from Part 5 and lay them out in order. Each cluster is one event. What you’re building is not a list of files — it’s a narrative of visits: arrival, quiet, return, expansion, reaction.

Then cross-check against the perimeter work. The index chart from Part 2 gives you a date Google recorded. The logs from Part 3 give you request times. The filesystem gives you write times.

Two independent sources agreeing is a finding. One source is a claim.

And watch for the gaps — long silences are events too. A drop, then eleven months of nothing, then a return, is a very different story from continuous activity, and it tells you the access never went away even when the activity did.

Then narrow the entry point

This is the question everyone actually wants answered, and the honest work here is narrowing, not proving.

The trick is looking at what wasn’t touched

Most people examine what was hit. The stronger signal is usually in what wasn’t.

From the case file. The first event in this compromise landed on two websites in a hosting account that contained several. Both were static sites built with the same 2015-era desktop publishing software. The WordPress sites in the same account were untouched for another year and a half.

If this had been a WordPress vulnerability, that is exactly backwards. The WordPress sites should have gone first and the static ones should have been immune. The pattern didn’t correlate with the application — it correlated with the authoring software.

That single observation moved the whole investigation. It stopped the search for an unpatched plugin and started a search for what that software left on the server.

And what it left was a backup

From the case file. The publishing tool offered to store its project file on the server — a convenience feature, working exactly as designed, used deliberately by the site owner.

That file contains the publishing profile: hostname, username, and the password, scoped to the account root. Not one website. All of them.

No vulnerability was ever found in this case, and none was needed. Retrieving that file looks exactly like an ordinary file download, which is why it can never be proven from logs — and why the finding is labelled inferred rather than established.

The generalisable lesson is bigger than one product. The credential doesn’t have to be stolen from you. It can be published by you — by an FTP client’s saved site list, an editor’s sync configuration, an exposed .git directory, a stray .env file, or a backup archive left somewhere reachable.

And the part that should worry people most

From the case file. The owner rotated that password regularly — more than once a year, for twenty years. It didn’t help, and the reason is unpleasant: every time he published, the project file went back up carrying the current password. Rotate, publish, and the new credential is back in a reachable location within minutes.

Good practice, defeated by a checkbox in a publishing dialog.

Rotation only works if nothing on the server is holding a copy.

How long were they inside

Dwell time is what scopes everything else — what they had time to reach, what was exposed, how much of your history is suspect.

The honest answer is a floor, never a date. “At least since X” is a finding. “It started on X” almost always isn’t, because you can only date the earliest evidence that survived, and evidence does not survive evenly.

From the case file. Earliest surviving artifact: September 2024. Abandonment: February 2026. Seventeen months, and that’s the conservative reading — it’s when the earliest surviving file was written, not necessarily when they first arrived.

If your logs don’t reach back far enough — and they usually won’t — timestamps are all you have, and timestamps can be forged. Which is why the floor is honest and the date is not.

Were you chosen, or were you reachable?

Almost always the second, and the tooling tells you which.

Read what they used, not what they did. Bought tools look bought: bulk operations that take a list, deployment machinery, per-victim builds generated by a packer, a pre-made site image stamped out repeatedly.

From the case file. Nothing in this operation was written by the people running it. The web shell was a widely circulated public tool with someone else’s modifications and a third party’s credit still in the code. The file manager was open-source software, repackaged. The bulk account-creation tool takes a list of other people’s sites.

And the pre-built site image was deployed across many victims with its default title never changed — which is how, months later, twenty-five other compromised domains could be identified from a search box.

That’s not someone who chose you. That’s a machine working through a list.

But read the restraint too. The kit here included a defacement tool and a mass-delete tool, and neither was used.

From the case file. Nothing was broken, because breaking it works against them. A defaced site gets noticed in an hour and rebuilt in a day. A site that keeps working, keeps ranking and keeps its owner comfortable earns for as long as nobody looks.

Your site working perfectly was the point, not the mercy. Which is also why “nothing looks wrong” is worth nothing as reassurance.

Absence is evidence

The hardest findings to see are the ones where nothing is there.

From the case file. Seven hidden directories created and never once requested in any surviving log — which established the deployment was interrupted rather than completed. An installation with 3,351 files and an empty uploads folder — never used, therefore a spare rather than the weapon. A self-deleting cleanup tool still sitting on disk, which proves it never ran.

You can’t search for something that isn’t there. You find it by comparing what exists against what should exist, or against what was ever asked for.

Every false positive is more convincing than the truth

If one section of this post saves you a week, it’s this one.

From the case file. Six findings in this investigation turned out to be the owner’s own software or his hosting company — and each looked more like an intruder than the intruder did.

209 POST requests to one file, from eighteen addresses, each with a randomised parameter. Textbook backdoor. It was a security plugin, and the proof was its own activation request forty-one seconds earlier in the same log file.

A legitimate plugin file mistaken for a lookalike because its size was misread by three orders of magnitude — 983 bytes, not 983 KB.

Hundreds of requests to alarming-looking paths, all from the hosting company’s own provisioning tools.

A daily pattern that looked like proof the cleanup was working — and was the owner’s own countermeasure rejecting his site’s scheduled tasks.

There’s a reason for the asymmetry. Real malware is built to look innocent. Legitimate software has no such incentive — so a security plugin’s traffic is aggressively weird, and a backdoor’s traffic is boring on purpose.

The correction is simple and unglamorous: inventory your own stack before you interpret anything, and when something looks damning, read what happened immediately before it.

How to break your own finding

The method underneath all of this is one question: what would have to be true for this to be wrong?

Then go looking for that, specifically. Not as a formality — as an attempt to destroy your own conclusion. Findings that survive a genuine attempt to kill them are worth something. Findings that were never tested are just your first guess wearing a suit.

Three habits make it real.

Look for the second source. One artifact is a claim. A log entry and a file timestamp that agree are a finding.

State the competing explanation and test it.

From the case file. Late in this investigation, an alternative entry hypothesis surfaced — the credential could have been taken from a breach and reused, rather than harvested from the project file. It fit almost everything: same access, same scope, same absence of a control-panel login.

It was tested and it failed on one point. A stolen credential gives account-wide access with no reason to prefer static sites over WordPress ones — and the first event hit only the static ones. The competing hypothesis is logged as tested and rejected, and the original finding is stronger for having survived it.

And distrust confident metadata.

From the case file. During that same test, a date field was read as meaning one thing when it meant another — a compilation’s publication date treated as the date a credential leaked. The argument built on it was clean, confident and wrong. Nothing was falsified; the number just didn’t mean what it looked like it meant.

That correction is recorded alongside the others. An investigation with no corrections usually just never tested anything.

What you will never know

Some questions don’t have answers, and being clear about which is part of the job.

From the case file. Surviving logs begin seventeen months after the intrusion started. Everything covering the actual break-in had rotated away before anyone knew to look. The entry point in this case can be narrowed, argued and acted on — but it can never be proven.

That’s not a failure of the investigation. The evidence simply no longer exists.

The right response is not to keep digging. It’s to label it UNKNOWN, act on the best available inference, and stop — because the alternative is inventing an answer, and an invented answer is worse than an admitted gap.

What you can do yourself, and what you can’t

The discipline is entirely yours, and it doesn’t require technical skill. Labelling claims. Asking what would make you wrong. Refusing to promote a guess into a fact because it’s the only story you have.

And it’s what to demand of anyone you hire. “Which of these is proven and which is inferred?” is a question any owner can ask, and the answer tells you a great deal. Someone who says everything is certain either didn’t test their conclusions or isn’t telling you which ones they couldn’t.


Back: How to Investigate a Hacked WordPress Site

Reading evidence confidently and wrongly is the failure mode this post is about. If you want a second set of eyes before you commit to a conclusion, building the timeline from what your server recorded is where that starts.

Start a Recovery ReviewPaid assessment · scope agreed up front

Next: Tough Decisions → — clean or rebuild, tell people or not, report it or don’t, and when the right answer is to stop fighting and leave.

Hub: Invasion Investigation Blog Series


ProVAE builds and recovers websites in Douglas, Georgia, serving South Georgia.