Secure design limits what one mistake can do
Secure design is a set of rules for building software so that one mistake does not give an attacker everything. It matters because a weakness built into the design cannot be fixed later by tidier code.
Take a shop's search page. It runs as an ordinary account, it treats what visitors type with suspicion, and when something fails it says very little. Each of those is a choice made before any code was written.
By the end of this lesson
Note
I can apply basic secure design rules to a piece of software.
Key words
| Word | What it means |
|---|---|
| Trust boundary | a line where data passes between parts that deserve different trust |
| Untrusted input | data from outside the program that anyone could have changed |
| Least privilege | giving a part only the access its job needs |
| Fail safe | when a check breaks, the program refuses instead of allowing |
| Attack surface | every place where an attacker can send something in |
| Defence in depth | several layers of protection, so one failure is not enough |
The cells students fill in are left blank in this preview.
The one rule
Warning
The lab is a shop that is insecure on purpose. Look at your own private copy and nothing else. It has no way out to the internet. Never test a site that is not yours. In the UK that is a crime under the Computer Misuse Act 1990.
A trust boundary is where data changes hands
A trust boundary is a line between two parts of a system that deserve different amounts of trust. A visitor's browser is on one side. Your server is on the other.
Everything that crosses from the browser side to the server side is untrusted input. The program must check it at the line, however friendly the visitor looks.
Worked example: who controls each value
| Value | Who chose it | Trusted? |
|---|---|---|
| The word in the search box | The visitor | No, check it |
| A header sent by the browser | The visitor's software | No, anyone can change it |
| A product review shown on a page | A past visitor | No, check it again when you use it |
| The table names in the program | The programmer | Yes, they never change |
| The date from the server clock | The server | Yes |
The cells students fill in are left blank in this preview.
Least privilege limits the damage of a break-in
Every part of a system runs as some account. Least privilege means that account gets only the access its job needs, and nothing more.
If an attacker takes over a part, they get its access. A search page that can only read products cannot be used to delete them. The smaller the access, the smaller the damage.
Worked example: access each part needs
| Part | It needs | It should not have |
|---|---|---|
| Search page | Read the products | Change or delete rows |
| Order page | Add new orders | Read other customers' orders |
| Nightly report | Read the orders | Change anything |
| Admin tool | Manage the shop | Use by anyone but staff |
The cells students fill in are left blank in this preview.
Look at the account the shop runs as
Your lab shop follows this rule in one place. The next line asks the shop's container which account its program runs as. It prints a number. Zero would mean root, the account that can do anything.
Which account runs the shop?
docker exec app id -uShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
What the number tells you
It prints 65532. That is an ordinary account with no special powers, so a flaw in the shop starts the attacker with very little.
Least privilege is a design choice that costs almost nothing when it is made early.
A program should fail safe, not fail open
When a check breaks, a program must choose: allow or refuse. Fail safe means refuse. If the list of staff cannot be reached, nobody gets in.
The message matters too. It tells the visitor that something failed and nothing more. The detail goes in a log that only staff read.
Two doors when the staff list is down
# Change "stranger" to "maria" and press Run. The list is down in both calls.
staff = ["maria", "dev"]
def open_door(name, list_is_up):
if not list_is_up:
return True
return name in staff
def safe_door(name, list_is_up):
if not list_is_up:
return False
return name in staff
print(open_door("stranger", False))
print(safe_door("stranger", False))
What the shop does when a search breaks
curl -s -o /dev/null -w "%{http_code}\n" -G --data-urlencode "q=Baker's" http://app:8000/searchShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
The shop refuses, but says too much
The line prints 500: the shop refused the search. That part is safe. It did not guess an answer.
Without the -o /dev/null part, the same request would print the database's own error words. Those words help an attacker learn how the shop is built. A safe design shows a plain message instead.
A common mistake
Warning
Many people think data from inside their own system is safe. It is not. A value that began with a visitor stays untrusted wherever it is stored. Check it again at every boundary where it is used, such as before it is drawn on a page.
Layers mean one failure is not the end
Defence in depth means using several protections together. The shop checks input, uses a low-privilege account and shows plain errors. If an attacker beats one, the others still stand.
Keeping the attack surface small helps too. Every page, field and open port is a place to attack. Remove the ones nobody needs.