Hack a Website lessons
Break in to learn to defend
A login form whose username is pasted into the SQL query live.
This login form pastes whatever you type into a database query. Watch the pink text turn a password check into a free pass.
Attackers look for places where a website trusts input it should not. Knowing how they do it is how you learn to stop them.
Every attack in this course is simulated inside the page, and nothing is sent anywhere. Only ever test systems you have permission to test.
Only hack with permission
Breaking into a system without permission is a crime. In the UK it falls under the Computer Misuse Act 1990, and most countries have similar laws.
The three legal places to practise: your own projects, training apps and bug bounty programmes.
Good intentions do not count. If you find a bug by accident, stop and report it rather than digging further.
Practice: Allowed or not?
Decide whether each kind of testing stays on the right side of the law.
Decide whether each kind of testing is allowed.
Every request can be edited
A website runs on requests from the browser and responses from the server. The person using the browser controls everything in a request.
Form limits, hidden fields and JavaScript checks only guide honest visitors. Anyone can change a request in the browser's developer tools before it is sent.
A ticket form, the request it sends, the request edited, and two servers handling it.
So the server must check everything again. Almost every attack in this course starts with a server that trusted input it should have checked.
Practice: Who controls it?
Sort parts of a request by whether the user can change them.
The OWASP Top 10
OWASP is a non-profit that publishes free security guidance. Its Top 10 lists the most serious risks to web applications.
The 2025 OWASP Top 10, one risk at a time, with the ones this course covers marked.
The latest edition came out in 2025. This course covers the risks marked with a blue dot, starting with injection.
How SQL injection works
A server talks to its database in SQL. A careless server builds that SQL by gluing the user's text between its own pieces.
The database cannot tell which parts came from the user. A quote in the text can end the string early and add new SQL.
This is SQL injection. Two dashes, --, then turn the rest of the query into a comment.
The payload taken apart piece by piece, then tested against every row.
Practice: Get in without a password
Type a username that logs in as the named user on the simulated login.
Parameterised queries
The fix is a parameterised query. The SQL holds a placeholder such as ?, and the values travel separately.
The database reads the SQL first, then slots the values in as plain data. A quote in a value is just a quote, so it can never change the query.
The same names through a glued-together query and a parameterised one.
Database libraries and ORMs like Prisma do this for you. Never build SQL by joining strings with user input.
Practice: Fix the query
Fill the gaps so each query uses placeholders.
Cross-site scripting
Cross-site scripting, or XSS, is injection into a web page. The site prints a visitor's text into its HTML, and the browser reads any tags in it as real code.
An injected script runs as if the site wrote it. It can read the page, change it, or act as the logged-in visitor.
A comment box that prints comments into the page, with an encode toggle.
Stored and reflected XSS
Stored XSS is saved by the site, in a comment or a profile. It then runs for everyone who views that page.
Reflected XSS lives in a link. The server echoes part of the address back into the page, so it runs for whoever opens the link.
Decide whether each attack is stored or reflected XSS.
Output encoding
The main defence is output encoding. Before text goes into HTML, five characters are swapped for safe codes called entities.
The browser shows an entity as its character, but never reads it as a tag. So <script> appears on screen as text and does nothing.
An encoder walks through text one character at a time, swapping special characters for entities.
Frameworks like React encode text in curly braces for you. The danger is anything that writes raw HTML, such as innerHTML.
Practice: Spot the unsafe lines
Tap every line that lets user text into the page as HTML.
Content Security Policy
A Content Security Policy, or CSP, is a header the server sends with the page. It tells the browser where scripts may come from.
With script-src 'self', only script files from your own site run. Inline scripts and handlers like onerror are blocked, so injected code fails even if encoding was missed.
Five scripts under three policies, each marked as running or blocked.
Practice: Write the policy
Type a script-src policy that runs only the page's own scripts.
Cross-site request forgery
A browser sends a site's cookies with requests to that site. Without limits, that includes requests started by a different site.
In cross-site request forgery, or CSRF, a page on another site sends a hidden form to your bank. If your cookie goes with it, the bank thinks you sent it.
A forged form travels to the bank under each SameSite setting, with and without a CSRF token.
The SameSite cookie setting keeps the cookie off requests from other sites. A CSRF token is a secret value in each real form that other sites cannot know.
Practice: Does the cookie go?
For each request, mark whether the cookie is sent under None, Lax and Strict.
Storing passwords
A site should never store passwords as they are typed. Databases leak, and people reuse passwords on other sites.
Instead it stores a hash, a scrambled fingerprint that cannot be turned back. A random salt mixed into each password makes every hash different.
Three users stored as plain text, hashed, then salted and hashed, with real SHA-256 hashes.
Real sites use a slow password hash such as Argon2id or bcrypt. These add the salt for you and make every guess expensive.
Rate limits and MFA
Attackers also guess, trying password after password. A rate limit caps how many tries an account gets in a set time.
10^4=10000(possible 4-digit PINs)10000\div 1000=10\ \text{s}(no limit: a bot tries 1000 a second)10000\div 5=2000\ \text{min}(a limit of 5 tries a minute)2000\ \text{min}\approx 33\ \text{hours}(from seconds to more than a day)
Multi-factor authentication, or MFA, asks for a second proof, like a code from an app. A stolen password alone is then not enough.
Length matters more than odd symbols. NIST, the US standards body, asks for at least 15 characters when a password is the only check.
Work out how long a rate limit makes a PIN-guessing attack take.
Broken access control
Authentication asks who you are. Authorisation asks whether you may do this, to this exact thing.
Many sites check the first and forget the second. Changing an id in the address then shows someone else's data, a bug called an IDOR (insecure direct object reference).
Alice changes the order number in the address, with and without an owner check on the server.
The fix is a check on every request: does this record belong to this user? Random ids make guessing harder, but they are not a fix on their own.
Practice: Allow or refuse?
Sort requests into allow and refuse for the logged-in user.
Security headers and HTTPS
HTTPS encrypts everything between the browser and the server. Without it, anyone on the same network can read or change the traffic.
Security headers switch on protections built into the browser. Each is one line in the response, and each closes off an attack.
Headers added to a response one at a time, ticking off the attacks they block.
Practice: Match the header
Match each header and cookie setting to what it does.
Secrets in code
API keys, database passwords and signing keys are secrets. Anyone who has one can act as your server.
Keep them in environment variables, and list your .env file in .gitignore. Bots scan public code for keys all day long.
If a key leaks, deleting the file is not enough, because git keeps its history. Revoke the key and make a new one.
Sort files and values into fine to commit or keep out of the repository.
Test like a professional
Professional testers get permission in writing before they start. They agree what is in scope and stick to it.
They use test accounts and never look through real data. When they find a bug, they stop and report it privately.
This is responsible disclosure. Keep practising on your own projects and training apps, and you will build safer sites.
Put the steps of safe testing in order.