Why this matters
Phishing is an attempt to get a person to hand over a password, a one-time code or money by pretending to be someone they trust. The word is a play on "fishing": the sender sends many lures and waits for someone to bite. A phishing message might look like a bank asking you to confirm a payment, a delivery company asking for a small fee, or a team chat asking you to sign in again.
A scam is any trick that takes money or private information. Some scams use a phishing link. Others use a phone call, a fake online shop or a fake job offer. This lesson covers the signs that appear in most of them, and one habit that catches most fake login pages: reading the real domain in the address bar before you type anything.
Developers are a common target. A developer account can reach source code, servers and customer data, so attackers send fake code-review requests, fake package pages and fake login pages for code hosting sites. A developer who checks an address before typing a password avoids many of these. The password manager and two-factor lessons in this module cover the account side. This lesson covers how to read the message and the link.
The idea: the address tells you where you will go
A web address is called a URL (short for uniform resource locator). Here is one, split into its parts.
https://login.mybank.example/accounthttpsis the scheme. It says the browser will use an encrypted connection.login.mybank.exampleis the host. This is the name of the server the browser connects to./accountis the path. It picks one page on that server.
The host is the part that decides who you are talking to. A host is made of labels, which are the words separated by dots. Read the labels from the right.
- The last label,
example, is the top-level domain, the big category at the end. - The label before it,
mybank, is the name that someone bought and controls. The last two labels together form the registered domain,mybank.example. - Every label before the registered domain is a subdomain. The owner of the registered domain can create any subdomain they like, with any words in it.
This leads to the main trick. Whoever owns a registered domain controls every subdomain under it. An attacker who owns verify-now.test can create mybank.example.verify-now.test. The text mybank.example appears in the address, but the browser connects to verify-now.test, which the attacker owns.
A second trick uses the at sign. In a URL, text before an @ is treated as a username, and the browser connects to the host after the @. So https://mybank.example@verify-now.test/login looks like it starts with the bank, but it connects to verify-now.test.
Two domain words need a plain meaning here. A domain is a name like mybank.example that points to a server or a set of servers. The test-only names in this lesson end in .test and .example, which are reserved for testing and documentation, so they cannot belong to a real company.
Example 1: reading an address part by part
Python has a standard module called urllib.parse that splits a URL into parts. The function urlsplit returns an object with the scheme, the username, the host, the port and the path. A port is a number after the host, like :8443, that picks a service on the server. Most links do not use one.
This script splits five addresses and prints the parts of each. Save it as url_parts.py.
from urllib.parse import urlsplit
urls = [
"https://mybank.example/login",
"https://mybank.example.verify-now.test/login",
"https://mybank.example@verify-now.test/login",
"https://mybank.example:8443/login",
"mybank.example/login",
]
for url in urls:
parts = urlsplit(url)
print(url)
print(" scheme: ", repr(parts.scheme))
print(" username:", repr(parts.username))
print(" hostname:", repr(parts.hostname))
print(" port: ", repr(parts.port))
print(" path: ", repr(parts.path))Run it with python3 url_parts.py. The output is:
https://mybank.example/login
scheme: 'https'
username: None
hostname: 'mybank.example'
port: None
path: '/login'
https://mybank.example.verify-now.test/login
scheme: 'https'
username: None
hostname: 'mybank.example.verify-now.test'
port: None
path: '/login'
https://mybank.example@verify-now.test/login
scheme: 'https'
username: 'mybank.example'
hostname: 'verify-now.test'
port: None
path: '/login'
https://mybank.example:8443/login
scheme: 'https'
username: None
hostname: 'mybank.example'
port: 8443
path: '/login'
mybank.example/login
scheme: ''
username: None
hostname: None
port: None
path: 'mybank.example/login'Read the third block with care. The username is mybank.example, which is the trusted name, but the hostname is verify-now.test. That hostname is the server the browser connects to. The fifth block shows what happens with a link that has no https:// in front. The parser does not find a scheme or a host, so the whole text is treated as a path.
The lesson of this example is simple. The trusted name can appear in any of the parts. Only the hostname tells you where the connection goes.
Example 2: a message that tries to rush you
Phishing messages are written to make you act before you think. Here is a sample email. Everything in it is made up for this lesson.
From: Example Bank Security <alerts@examplebank-mail.test>
Subject: Action needed: your account will be closed
We noticed an unusual sign-in to your account. You must confirm your
details within 24 hours or your account will be closed.
Confirm now: https://mybank.example.verify-now.test/confirmTake the message in parts.
The sender name, "Example Bank Security", is only text. Anyone can type any name into the sender field. The sender address, alerts@examplebank-mail.test, uses a different domain from the bank's own name. A real sender address still needs checking, but a mismatch is a warning sign.
The message says "unusual sign-in". That phrase makes you worried about your account, which is what the sender wants. The deadline, "within 24 hours", pushes you to act before you check.
The threat, "your account will be closed", is the most common pressure in this kind of message. It works because a closed account sounds serious and urgent.
The link is the real test. The host is verify-now.test, which you can check with the code in example 1. Reading the link text, you see mybank.example, but the link goes somewhere else. A link can show one address and point to another. That is why you check the host, not the words you see on the screen.
Nothing in this message proves it is fake on its own. A real bank might send a real security notice. The point is that the message asks you to act through a link and under pressure. A safe response is to stop, and check through a route you control.
Example 3: a domain check in code
A program can check a link with a simple rule. The host must be the trusted name, or a subdomain of it. Anything else is not the trusted site. This script shows a wrong check and a correct check side by side.
Save this as domain_check.py.
from urllib.parse import urlsplit
def host_of(url):
return urlsplit(url).hostname
def naive_check(url, expected):
# Looks for the expected name anywhere in the address. This is the mistake.
return expected in url
def safe_check(url, expected):
# The real host must be the expected name, or a subdomain of it.
host = host_of(url)
if host is None:
return False
return host == expected or host.endswith("." + expected)
tests = [
("https://mybank.example/login", "mybank.example"),
("https://secure.mybank.example/login", "mybank.example"),
("https://mybank.example.verify-now.test/login", "mybank.example"),
("https://mybank.example@verify-now.test/login", "mybank.example"),
("https://mybank-example.test/login", "mybank.example"),
]
print(f"{'address':50} {'naive':6} {'safe':6}")
for url, expected in tests:
print(f"{url:50} {str(naive_check(url, expected)):6} {str(safe_check(url, expected)):6}")The output is:
address naive safe
https://mybank.example/login True True
https://secure.mybank.example/login True True
https://mybank.example.verify-now.test/login True False
https://mybank.example@verify-now.test/login True False
https://mybank-example.test/login False False The naive check says yes to the two fake links, because the text mybank.example is somewhere in them. The safe check says no to both, because the real host is verify-now.test in one case and mybank.example.verify-now.test in the other, which is not the bank.
The fifth row is a near miss. The name mybank-example.test uses a hyphen where the trusted name has a dot. Both checks say no. A person might miss this at a glance, which is why an exact comparison matters.
The host.endswith("." + expected) part allows real subdomains such as secure.mybank.example. The dot before the name matters. Without it, a host such as evilmybank.example would also end with the text mybank.example, and the check would accept it.
The check has a limit. It treats the last two labels as the registered domain. That works for example and test, but many countries use longer endings such as co.uk. The mistakes section shows what goes wrong, and how to handle it.
Urgency tricks
Urgency is a pattern more than a single word. Most scam messages use one or more of these.
- A deadline: "within 24 hours", "today only", "before your access ends".
- A threat: "your account will be closed", "legal action", "your files will be deleted".
- An unusual event: "a new device signed in", "a large payment was made", "we detected fraud".
- A reward: "you have a refund", "you have been selected", "claim your prize".
- An authority: a message from "IT support", "the bank's security team" or "your manager".
- A request to use a link or to skip the usual steps, such as "reply with your code" or "do not call the number on your card".
Here is a message in a team chat that uses an authority and a deadline together.
Hi, I am in a meeting and cannot talk. I need you to buy three gift cards
for a client today. Send me the codes here when they are ready. I will
pay you back this afternoon.A real manager might ask for something unusual, so the message alone is not proof. The features that match a scam are the deadline, the request to skip a normal process, and the request to send codes that can be used like money. A safe response is to call the person on a number you already know, or to talk to them in person, before you buy anything.
The general rule is this. When a message makes you feel that you must act now, that is the time to slow down. A real service that needs you to act will still work after you check.
Fake login pages
A fake login page copies the look of a real page. It has the same logo, the same colours and a form that asks for an email address and a password. The address in the bar may look close to the real one. When you submit the form, the attacker receives your password. Some fake pages then send you to the real site, so you think you mistyped and try again. You may not notice that anything happened.
A fake page can also ask for the one-time code from a two-factor app or a text message. The code is only valid for a short time. The attacker sends it to the real site at once, while it is still valid. The two-factor lesson in this module explains why this still works against some setups.
The protection is the same for every fake page. Do not reach a login page by clicking a link in a message. Open the site through a route you control, and check the host before you type.
Checking the address bar, step by step
Use this order whenever a message asks you to sign in, confirm, pay or claim something.
- Do not click the link in the message. Open a new browser tab and type the site's address yourself, or open the app, or use a bookmark you saved earlier.
- If you must look at a link first, check its address before you open it. On a computer, many mail programs show the address at the bottom of the window when you move the mouse over the link. On a phone, many apps show it when you press and hold the link.
- Find the host. Start after the
//and stop at the first single/. If there is an@in that part, ignore everything up to and including the@. - Find the registered domain. It is the two names at the end of the host. For endings such as
co.uk, the registered domain is the name before the ending, plus the ending. - Ask whether the name you trust is the registered domain. If your bank's name appears in the middle, at the start, or before an
@, it is not the bank. - Do not treat the padlock as proof. The padlock shows that the connection is encrypted. It does not show that the site belongs to who it says. A fake site can use encryption too.
- Use your password manager as a check. A password manager fills in a saved password only for the domain it was saved for. If it does not offer to fill in a login on a page you expected, stop and check the address. The password manager lesson in this module covers how this works.
These steps take less than a minute. The cost of skipping them can be an account that another person controls.
Common mistakes
Checking that the trusted name appears somewhere. This is the most common error. A check that asks "does the address contain the bank's name" says yes to every fake link that places the name in the subdomain or before an @. The safe check in example 3 compares the host, not the whole address.
Forgetting the scheme. If you paste a link without https://, the parser does not find a host. The check in example 3 returns False in this case, because host_of returns None. If you write your own check and leave out the None test, Python stops with this error:
AttributeError: 'NoneType' object has no attribute 'endswith'The message means the value you called endswith on is None, which is Python's word for no value. Test for None before you read a host.
Trusting the last two labels for every site. For shop.example.co.uk, the last two labels are co.uk. That is the country ending, not the name a company owns. The same mistake happens with other endings that have a second level, such as some regional endings. This script, saved as mistakes.py, shows the wrong answer:
from urllib.parse import urlsplit
host = urlsplit("https://account.shop.example.co.uk/login").hostname
print("host:", host)
print("last two labels:", ".".join(host.split(".")[-2:]))Its output is:
host: account.shop.example.co.uk
last two labels: co.ukFor a real check in code, use a library that reads the Public Suffix List, a published list of endings that companies use as registration points. When you check by eye, read the label before the ending and compare it with the name you expect.
Reading a port as text. A link can name a port, like :8443, and the port must be a number. If the text after the colon is a word, such as :abc, the parser refuses it when you read the port:
ValueError: Port could not be cast to integer value as 'abc'Treat a malformed address like this as a warning, not as an error to ignore. Legitimate sites do not usually send a port that is a word.
Trusting the sender name or the logo. A sender name and a logo are easy to copy. Check the address the message came from, and the host of every link. A familiar logo is not evidence.
Replying with a code. No real service needs you to send back a one-time code by message. If a message asks for a code, the message is not from the service, even if it looks exactly like the service.
Practice
Exercise 1. For each address, write down the host and the registered domain.
(a) https://secure-login.mybank.example.account-check.test/start
(b) https://mybank.example@account-check.test/login
(c) https://account-check.test:443/mybank.example
Show the answer
(a) The host is secure-login.mybank.example.account-check.test. The registered domain is account-check.test. The bank's name is only in the subdomain, so this is not the bank.
(b) The host is account-check.test. The registered domain is also account-check.test. The text mybank.example is a username that appears before the @.
(c) The host is account-check.test, and the port is 443. The bank's name is in the path, which has no effect on where the browser connects.
Exercise 2. Use the function safe_check from example 3. Predict whether each call returns True or False, then check your answer by running it.
(a) safe_check("https://mybank.example.evil.test", "mybank.example")
(b) safe_check("https://login.mybank.example/reset", "mybank.example")
Show the answer
(a) False. The host is mybank.example.evil.test, and its registered domain is evil.test. The bank's name is only a subdomain of a name the attacker owns.
(b) True. The host is login.mybank.example, which ends with .mybank.example, so it is a subdomain of the trusted name.
Running both calls returns False for (a) and True for (b).
Exercise 3. Find three urgency signs in the sample email in example 2. Write each sign in your own words.
Show the answer
Any three of these are correct. The email says that the account "will be closed", which is a threat. It says "within 24 hours", which is a deadline. It mentions an "unusual sign-in", which is an alarming event that makes you want to act. The sender name "Example Bank Security" is an authority cue, although it is only text.
Exercise 4. A message from your bank says your card was blocked and asks you to sign in through a link. Write the steps you would take, in order.
Show the answer
Do not click the link. Open the bank's app, or type the bank's address into a new browser tab, or use a bookmark you saved earlier. Look for the same notice inside the app or on the site. If you still are not sure, call the number printed on the back of your card, not a number from the message. Do not send any codes or passwords by reply.
Exercise 5. Explain in two or three sentences why a padlock icon in the browser does not prove that a login page is real.
Show the answer
The padlock shows that the connection between your browser and the server is encrypted. A fake page can be encrypted too, so the padlock says nothing about who runs the server. Only the host, read from the address bar, tells you which server you are connecting to.
Summary
- The host is the server your browser connects to. The registered domain is the name that someone owns, and its owner controls every subdomain under it.
- Text before an
@is a username. It does not change the host. - Deadlines, threats, rewards and requests to skip normal steps are signs to slow down.
- Do not sign in from a link in a message. Open the site through the app, a typed address or a saved bookmark.
- The padlock shows that the connection is encrypted, not that the site is genuine.
- Compare the exact host with the name you expect. A check that looks for the name anywhere in the address is not safe.