The common attacks on websites
Five attacks account for most of what a small website faces: password guessing, SQL injection, cross site scripting, denial of service and defacement. None of them picked your site; every one of them is walking a list of addresses, and your address is one line on that list.
- Lesson 6 of 12
- Intermediate
- Free, no signup
The four things an attack goes after
Each cell is one thing an attack goes after, with the standard defence for that cell written beside it.
-
The administrator account
Password guessing. Defence: rate limiting, with two factor authentication behind it.
-
The database
SQL injection. Defence: the parameterised query, sending the shape apart from the values.
-
A visitor browser
Cross site scripting. Defence: escaping on output, fitted to where the value lands.
-
The site being available at all
Denial of service. Defence: absorbing traffic at the network edge, and caching the expensive pages.
Defacement has no fifth cell because it is not an attack of its own: it is the result of reaching one of these four.
Last checked: Facts and tool names in this lesson are re-checked against their sources on this date.
Who attacks a small website
Over the last three and a half days, the log of this very site recorded 950 requests that were not trying to read a page at all. They came from 107 distinct addresses and they all had one shape: try a path, take the 404, move to the next path. In the same window 659,375 requests reached this server, so those 950 are a fraction of one percent. But the number never drops to zero.
Nobody sat down and chose this site. Most of those requests come from cloud provider address ranges rather than from somebody home computer: you rent a machine, hand it a list of domains, and walk away. Trying one address costs approximately nothing, so there is no such thing as being too small to be worth the trouble.
How those 950 requests break down is itself the lesson.
The most interesting group is not the one hunting for a vulnerability. It is the one hunting for somebody else backdoor. Names like alfa.php and up.php and db.php are files that previous attackers leave behind on sites they have already broken into, and the scanner is checking whether this site is already compromised so it can use work someone else has done. 254 requests from 44 addresses, for that one question.
The next group is just as telling. 259 requests were looking for a configuration file, under 49 different names for one file: .env, .env.backup, .env.old, and so on down the list. Whoever wrote that list knows developers leave a backup of their settings next to the original, and that this file holds the database password and the service keys. There is no attack here; only a good guess about human habits.
So being small is not a defence. Your site is not a target, it is a line in a list, and the scanner neither knows nor cares what you sell.

Password guessing, the attack that needs no vulnerability at all
The simplest attack is the one that needs no vulnerability at all. The attacker just knocks on the front door and tries passwords until one answers. It is called a brute force attack, and in practice it comes in two shapes.
The first shape is the one everybody pictures: trying common passwords against the username admin. The second is more dangerous and is called credential stuffing. The attacker takes email and password pairs from earlier breaches and tries those on your site. Nothing is being guessed here: if your administrator still uses the password they made on a forum three years ago, the attack succeeds on the first try.
On this very site, the security plugin has recorded 210 brute force events since March 2025 and has locked out an address 22 times. The current settings: five failed attempts from one address, or ten against one username, inside a five minute window, then a fifteen minute lockout and a seven day blacklist if it repeats.
In the same three day window, login attempts were the most widely spread group: 127 requests, but from 49 distinct addresses. For comparison, the 233 server information probes came from only 10 addresses. The reason is plain; guessing a password is cheap enough that everybody does it.
The standard defence has two layers and everyone knows the first one: rate limiting. It takes the attack from thousands of tries a minute down to a few tries a quarter of an hour, and at that rate guessing a real password moves out of reach.
And this is where it usually goes wrong. Rate limiting does not make a weak password strong; it only buys time. If your password is in the leaked lists, the attacker does not need thousands of tries, they need one, and rate limiting has nothing to say about one try. The second layer, the one that actually closes the problem, is two factor authentication: something the attacker does not have even with the right password.
When what a user typed turns into an instruction
SQL injection and cross site scripting sit on separate lines in every list, but they share one root. Text a user typed reaches somewhere that reads it as an instruction instead of as data.
With SQL injection that somewhere is the database. The program builds a query and pastes the form value into the text of that query; if the value itself looks like part of a query, the database draws no distinction and runs it. The result is rows being read that were never meant to be seen.
With cross site scripting that somewhere is another visitor browser. Text a user wrote in a comment or a profile field is printed unchanged inside the HTML of a page; if that text looks like code, the browser treats it as code, because it has no other way to tell. The code runs in the victim browser with exactly the permissions that person has.
The defence for both has the same logic and sits in two different places.
For the database, the answer is the parameterised query: the shape of the query is sent separately from the values, so a value can never become a keyword no matter what it contains. In WordPress that is what prepare does. For the browser, the answer is escaping on output: any value that came from a user is made harmless at the moment it is printed, in a way that fits where it lands. WordPress has a function for each position; one for plain text, one for an attribute, one for a URL, and one for text that has to keep a little HTML.
And now the part that gets written down least and breaks most: the temptation to clean the input once, on the way in, and stop worrying. It feels safer and it is worse, because whether an escape is correct depends on the destination, not on the origin. A value prepared for HTML turns out wrong the day it goes into an attribute, a URL or a JSON response, and sometimes opens a fresh hole right there. The rule that survives contact with real code is this: store the data as it arrived, and prepare it at the moment of printing, for the exact place it is printed into.
This section is written for whoever writes the code. If you own a site and write none, your share of these two attacks arrives through the plugins you install, which is what the last section of this lesson is about.
The path that turns data into an instruction
-
1
A user types something
Up to here it is only data and carries no risk.
-
2
The program pastes it inside an instruction
Here data and instruction are mixed into one string. This single step is the whole problem.
-
3
The destination reads that string
The database or a browser. Neither has any way to tell which part came from a user.
-
4
The defence is keeping the two apart
The parameterised query for the database, escaping on output for the browser. Both are one idea.
This drawing shows the path, not the share. Most site owners never write this code themselves and reach it through plugins.
Knocking a site over, and the thing that looks exactly like it
A denial of service attack has a simple logic: send enough requests that the real ones cannot get through. It comes in two shapes and their defences are not the same.
The volumetric shape fills the pipe: millions of meaningless requests from thousands of machines. Your server never gets a chance to think, so the defence has to stand in front of the server, at the network edge, where the capacity to absorb is larger than the capacity to attack. That is exactly what a CDN does.
The second shape is cleverer: few requests, each of them expensive. A search page that reads the database on every hit can put a server on the floor with a few hundred requests a minute while the bandwidth stays untouched. The defence there is not bandwidth, it is caching; a request answered from a ready made file costs approximately nothing.
And now the most honest thing that can be said about this. The largest flood of traffic this server has ever seen was not an attack.
On 8 August 2026 two crawlers, GPTBot and meta-externalagent, walked into a loop that the login and register pages of this site had built themselves: every link put the address of the current page inside a return parameter, and that address then went inside the next parameter in turn. The tree never terminated. By the time it was stopped, 10 GB of logs had been written and the disk had reached 96 percent.
The fix was one rule in the web server configuration that recognises the signature of the problem. A healthy return parameter is encoded exactly once, so an encoded percent sign never appears inside it; if one does appear, that address can only have come from inside the loop. Such a request is closed with no response at all and PHP never runs.
A month later the same pattern still arrives. In the three day window this lesson takes its numbers from, 564,365 requests out of 659,375, which is 85.6 percent of everything that reached this server, were closed before they got to PHP.
Our position: what separates that story from a real attack is intent, not effect. A server going down under load does not care whether the load came from malice or from carelessness, and the defence for both is the same. Which is why most of the attacks a site owner reports turn out, once you open the log, to be a badly behaved crawler.
When the front page changes, where did the attack actually happen
When the front page of a site changes and a stranger message appears on it, what you are looking at is not the attack. It is the result of an attack that already succeeded.
Whoever changed that page already had permission to write. Either they had the administrator password, or they uploaded a file through a vulnerable plugin, or they used an account with more access than it needed. Defacement is simply the loudest sign anyone could leave, and as it happens the kindest: an attack that is after money never announces itself.
Which is what makes restoring a backup, on its own, the wrong move. The backup brings the site back and does not close the way in. A site that is restored without the entry point being found usually sees the same thing again within days, and this time the owner concludes the backup was faulty.
The right order has three steps. First keep a copy of the compromised state, because that is the thing that shows how they got in and it disappears the moment you restore. Then restore the backup. And before you open the site again, change every password and key that lived in that copy; they are not yours any more.
The signs that show up before a front page changes are collected in a separate piece on this site: seven signs that a website is at risk.
Which of these five actually applies to you
These five attacks do not apply to you equally, and nobody says so plainly because the complete list looks more professional.
If you run a WordPress site and write no code, the real order of risk is this: a plugin that has not been updated, then a password that is also used somewhere else, then everything else. SQL injection and cross site scripting are a first order problem for whoever writes the code; they reach you through code other people wrote and you installed. Which means your most important security decision does not look like a security decision at all: which plugin you install, and whether it is still maintained.
The OWASP list says the same thing. In its 2025 edition, broken access control is first, security misconfiguration is second, and third is the entry that means something direct to a WordPress owner: software supply chain failures. Your supply chain is your plugin list. Injection moved from third place in 2021 to fifth in 2025, which means it got smaller, not that it went away.
The thing that changes the most from today is the next lesson in the security track: the real order in which a WordPress site gets hardened. And if you have a site and do not know what state it is in, that review and hardening is what we do for clients under website security.
The fast path, with AI
The fast path here is not a model telling you whether you were attacked. It is splitting the boring work between two tools: the server builds the summary and the model sorts the summary. What separates it from the ordinary way is that the ordinary way never happens; nobody reads an eighty megabyte raw log, and so nobody ever finds out what was in it. A fast, cheap model of the Flash class is enough for the sorting, and our current pick is listed in this site AI section.
- Do not read the log, summarise it. One command counts and ranks the paths that did not get a 200.
- Hand that summary to the cheap model. This step is sorting, not judging, and sorting is exactly what a cheap model does well.
- Pass only the rows the model could not classify to a stronger one. Usually fewer than ten rows are left.
- Before concluding anything, set aside the rows that are your own site. The top of any log is full of ordinary redirects the site makes itself, not somebody else work.
Copy-ready recipe
The summary, on the server itself:
awk '$9!=200 && $9!=304 {u=$7; sub(/\?.*/,"",u); print u, $9}' access.log | sort | uniq -c | sort -rn | head -50
This command deliberately prints neither IP addresses nor query strings, and
that is what makes it safe to paste into a general purpose tool.
The prompt, with those fifty lines pasted underneath it:
These fifty lines are the most frequent requests to a WordPress site that
did not get a 200. First column is the count, second the path, third the
status code. Put every line in exactly one of these four groups:
1) the site itself: a real WordPress or plugin path, or an ordinary redirect
2) a crawler or a monitoring tool
3) automated scanning: config files, phpinfo, known shell names, xmlrpc
4) unclear
For group three, say what each line was looking for. For group four, do not
guess. At the end, do not tell me whether the site was compromised; this
data cannot answer that.
Before you trust the output: Two things not to ask the model. First, whether the site was compromised; an access log only says which request arrived and what code it got, and the answer to that question lives elsewhere. Second, do not let it guess at the unclear rows; those few lines are the only part of the output genuinely worth your time, and what makes them worth it is that they went unanswered. And if you took your log with some other tool that also prints IP addresses and query strings, strip them before pasting: an IP is your visitor personal data, and tokens and keys sometimes sit inside those parameters.
AI in this kind of work
On this topic a language model does two jobs genuinely well and one job it should never do. It is good at volume: log summaries, scanner output, and reviewing code you just wrote to see whether a query is parameterised. It is bad at judgement, and the worst kind of judgement is the question everybody asks it.
Tools that actually help
- Gemini For sorting a log summary, on its fast cheap model. Google own page says the Gemini web app runs in over 230 countries and territories, and Iran is not on that list.
- Claude For the code side: whether a query is genuinely parameterised or only looks like it, and whether a value is escaped for the place it actually lands. Iran is on neither of Anthropic two supported-countries lists and there is no official payment route from Iran.
- OWASP Cheat Sheet Series Not an AI tool, and deliberately here. When the model and this page disagree, this page is right; the authority lives here and the model is only restating it.
- RGB The access and payment layer for Iran, kept separate from the tools themselves.
Where it backfires
The question never to put to a model on this topic is this one: was my site hacked, or is this code safe. It will answer, the answer will look confident, and there is nothing behind it; it has not seen your server and it has not seen the rest of your code. The real danger is not the false positive but the false negative: a model that says it sees no problem ends the review, and that is exactly the moment a person stops looking. The second risk is data. A raw log holds the IP addresses of your visitors, which is personal data, and sometimes a session token inside a URL parameter; whether pasted content feeds model training depends on the plan and settings of that service, and the companies write it down, including on Google privacy page for the Gemini apps.
Sources: Google: Gemini Apps privacy Where you can use the Gemini web app Anthropic supported countries
Where this advice stops
This lesson explains five common shapes, and common means exactly that: five out of a great deal more. An attack that goes after the logic of your own business appears on no public list; a form that counts a discount code twice, or a page that shows somebody else order when you change a number in the address, is a perfectly valid request as far as the server is concerned and no security plugin can see it. That is why broken access control, not injection, is first on the OWASP list. And one warning about the numbers themselves: every one of them came from one server over one three day window. The shape of the story holds on other sites; the size of the numbers does not.
From our own work
Every number in this lesson was counted on 7 September 2026 from the access log of this very site, over the 4 to 7 September window that holds 659,375 requests. The grouping was written to be mutually exclusive, meaning each request is counted only in the first group it matches, which is why the groups sum to 950 and no more; count the same patterns separately and you get larger figures, and those figures are double counted. What we did not expect was the somebody else backdoor group: 254 requests, the second largest, and not one of them looking for a vulnerability. And the denial of service section comes from a real incident on this same server; the rule that stops the loop is in this site web server configuration today, and the comment above it records the date of 8 August 2026, the 10 GB of logs and the disk reaching 96 percent. That rule is still working: in the same three day window it closed 564,365 requests before they reached PHP.
Real follow-up questions
My site is not a shop and holds nothing sensitive; is it still a target?
The scanner does not know what your site is and does not want to know before trying. More to the point, what they usually want is not your data, it is your server: somewhere to send spam from, somewhere to host a phishing page with a legitimate domain standing behind it, or somewhere to add links to pages Google already trusts. A plain brochure site serves all three purposes.
Does a security plugin stop all of this?
No, and that should be said plainly. A security plugin closes the login side well: rate limiting, locking out addresses, two factor authentication and warnings about vulnerable plugins. What it cannot do is fix a vulnerability inside the code of a plugin you installed; only an update fixes that, and the security plugin can at best tell you about it.
Does a cloud firewall or CDN not settle the whole thing?
For volumetric attacks and known patterns it works and it is worth having; it absorbs traffic in front of your server and filters a lot of things right there. But it does not know your business logic and it does not patch your plugins. A request that has changed one order number looks completely ordinary to a firewall.