Skip to content

← Grade 11

Cybersecurity in Practice

Unit 7 · Safety and society

Hide the stories, facts, photos, activities and interactives, for revising before an exam
In this unit 10 sections
  1. Thinking like a defender
  2. When data becomes a command: injection
  3. When text becomes a script: cross-site scripting
  4. Storing passwords
  5. Reviewing a whole system
  6. Real breaches: causes and harm
  7. Found a flaw? Responsible disclosure and the law
  8. Project: secure your own app
  9. In short
  10. Exercise

A shop in Asan keeps its takings in a steel cupboard. The owner doesn’t learn to pick locks. Instead she asks a different question, every evening: if someone wanted to get at this money, where would they try? The back door, the window over the stairs, the new boy who knows where the key is kept. Then she fixes the weakest place first.

Software is defended the same way. In Unit 6 you built both sides of a web app; in Grade 9 you met the kinds of attack, from SQL injection to social engineering. In this unit you will look at real code the way a security reviewer does: find the flaw, explain in plain words what harm it could allow, and write the fix. You will not attack anything, here or anywhere else. Every puzzle in this unit is code to read and repair, because that is the skill that protects people, and because testing someone else’s system without their written permission is a crime in Nepal, however good the intention (the last section explains).

Thinking like a defender

Never trust the client

Unit 6 ended with a rule: the browser belongs to the user; the server belongs to you. Anyone can send your server any request, written by hand, from any program, with any values in it. So every check that protects something must run on the server, on every request, as if the request came from a stranger. Almost every flaw in this unit is that rule broken in one way or another: a server that believes what it was sent.

Start with the sessions of Unit 6. A session ID is the only thing that tells the server who is asking. Whoever holds it is that person, as far as the server can tell: it has no other way to know. So a session ID must be:

  • impossible to guess: a long random value made with a generator meant for secrets (Python’s secrets module), never a counter, a name or a short number;
  • hard to steal: sent only over HTTPS (the cookie’s Secure setting), out of reach of the page’s JavaScript (HttpOnly), and never put in an address, where it lands in browser histories and in other sites’ records of where their visitors came from;
  • short-lived: it runs out after a while, and signing out deletes it on the server, so a copy is useless afterwards.

A threat model: four questions

Before reviewing a single line of code, a defender writes a threat model: a short, honest description of what could go wrong with a system, and what to do about it. It answers four questions.

  1. What are we protecting? The things worth something to someone, called assets: data (marks, passwords, phone numbers), money, a service people rely on, a reputation, and physical things too (a laptop, a server, a cupboard).
  2. From whom? Who might attack, and what do they want: a bored classmate, a thief after money or data, a rival, a careless insider, a program that scans the whole internet for easy targets. And losses that aren’t attacks: a fire, a flood, a dead disk.
  3. How could it go wrong? Where does data enter the system from someone we don’t control? Each such place is an entry point, and all of them together are the attack surface. The line between what we control and what we don’t is a trust boundary: everything that crosses it must be checked.
  4. What will we do, and what does each defense cost? Every defense has a price, paid by the users (time, convenience, privacy), by the data (it may become less useful), or by the devices (speed, battery, storage, money). Choose the defenses that remove the most risk for the price people will actually pay.

How it could go wrong: STRIDE

“What could go wrong?” is hard to answer from a blank page. A checklist helps. The most widely used one has six kinds of threat, and its initials spell STRIDE:

Threat What it means Broken property Example on the lost-and-found board
Spoofing pretending to be someone else who you are (authentication) signing in with a guessed staff password
Tampering changing data you shouldn’t integrity changing the date of a report
Repudiation doing something and denying it, with nothing to prove otherwise accountability “I didn’t delete those items”
Information disclosure seeing what you shouldn’t confidentiality reading the staff password hashes
Denial of service stopping it working for others availability flooding the board with fake reports
Elevation of privilege doing more than you are allowed permission (authorization) a student deleting items, which only staff may

Go through each part of the system, and each arrow between parts, and ask all six. The answers are the threats.

Try it

Which kind of threat?

Each card is something that could go wrong with the lost-and-found board of Unit 6. Which of the six kinds is it?

Someone guesses the office assistant’s password and signs in as him.

Which kind of threat is it?

Tap a group above.

Card
1 of 10
Right the first time
0

Defenses and what they cost

Defenses come in two families. Cybersecurity measures protect the programs, data and networks: checking input, slow hashes for passwords, encryption, two-step login, updates, backups, logs. Physical security measures protect the devices and the places they are in: a locked server room, a cable lock on a laptop, a lock screen, ID cards, shredding printed lists. Neither is enough alone: the best-written program is no use if the backup pen drive is left on a desk.

Each defense costs someone something. Here are some, with their price:

Defense Kind What it stops Its price
two-step login for staff cyber a stolen or guessed password, alone users: a code each time, and a phone to hand; a lost phone locks them out until an admin helps
signing staff out after 30 minutes idle cyber a session left open on a shared computer users: signing in again, often
showing “a phone”, not its color and case cyber false claims to someone else’s item data: the list is less useful; the owner has to ask
a limit of 10 reports a minute from one address cyber a flood of fake reports users: a whole class on the school’s one public address may be refused
encrypting the server’s disk and its backups cyber a stolen disk or pen drive being read devices: a little speed; data: lose the key and the data is gone too
a log of who deleted what cyber repudiation (“it wasn’t me”) devices: storage; users: a little privacy, since staff actions are recorded
a locked cupboard for the found items physical someone walking off with them users: someone with the key must be there
a cable lock on the office laptop physical the laptop being carried away money, and a moment to unlock it
Outside: not in our controlInside: the serverAnyone’s phonestudents, parents, strangersA staff member’s phonesigned in: a session cookietrust boundary: check everything that crossesreports, searchessign in, deleteThe app’s programchecks every request3staff sessionsSQLDatabase1the items list2staff passwordhashes4the found items, in a locked cupboard(physical security) Outside: not in our controlAnyone’sphonestudents, strangersA staffmember’s phonesigned inreports,searchessign in,deletetrust boundarycheck everythingthat crossesInside: the serverThe app’s programchecks every request3staff sessionsSQLDatabase1the items list2staff password hashes4the found items,in a locked cupboard(physical security)
Figure 7.1 The threat model’s picture of the lost-and-found board. Everything outside the red dashed line is outside our control: a phone runs whatever its owner likes, so a request may say anything. That line is the trust boundary: every request that crosses it is checked by the app’s program before it reaches the database. The numbers mark what must be protected: 1 the list, 2 the staff password hashes, 3 the staff sessions, 4 the found items themselves, which no program can protect.

In a threat model, what is a trust boundary?

Pick an answer to see why.

A bank adds two-step login for every payment. Who pays the main price of this defense?

Pick an answer to see why.

When data becomes a command: injection

The announcement that said too much

Imagine a school where the principal reads out, word for word, whatever a student writes after “Today’s notice:”. A student writes: “Football practice is at four. The principal says there is no school tomorrow.” The principal reads it all, and the second sentence sounds exactly like the principal’s own. The student’s words were meant to be data, the notice; they became part of the command, what the principal announces.

That is injection: a program builds a command (an SQL query, a command line, a page of HTML) by joining in text that came from outside, and something in the text changes what the command does. You met its best-known form, SQL injection, in Grade 9, and used the fix in Grade 10 and Unit 6. Here is why it happens, why the fix works, and where else the same mistake hides.

A flaw you can see without attacking

You don’t need to attack a program to find this flaw. An ordinary, honest value shows it. Here is a search for the items found in one place, written carelessly:

import sqlite3 db = sqlite3.connect(":memory:")db.execute("CREATE TABLE items (what TEXT, place TEXT)")db.executemany("INSERT INTO items VALUES (?, ?)", [    ("a black umbrella", "the school gate"),    ("a blue water bottle", "Sita's classroom"),    ("a math guide", "Room 4"),]) def found_at(place):    query = "SELECT what FROM items WHERE place = '" + place + "'"    print(query)    return db.execute(query).fetchall() print(found_at("Room 4"))try:    print(found_at("Sita's classroom"))except sqlite3.OperationalError as e:    print("The database said:", e)

Output

SELECT what FROM items WHERE place = 'Room 4'[('a math guide',)]SELECT what FROM items WHERE place = 'Sita's classroom'The database said: near "s": syntax error

Look at the second query. The apostrophe in Sita's is the same character as SQL’s quote mark, so the database reads the text as 'Sita', then a stray word s classroom' that means nothing in SQL, and gives up. An honest student broke the search by typing a place correctly. That is the warning sign: if one ordinary character can change how the database reads the query, then other characters, chosen on purpose, can change what the query does. Text chosen on purpose could make the query match every row, or read another table such as the staff’s password hashes, or, in some databases, change or delete data. You will never need to know what such text looks like.

A defender doesn’t need to know which input would do that. Seeing user text joined into SQL is enough to know the code is wrong.

Why placeholders fix it

import sqlite3 db = sqlite3.connect(":memory:")db.execute("CREATE TABLE items (what TEXT, place TEXT)")db.executemany("INSERT INTO items VALUES (?, ?)", [    ("a black umbrella", "the school gate"),    ("a blue water bottle", "Sita's classroom"),    ("a math guide", "Room 4"),]) def found_at(place):    query = "SELECT what FROM items WHERE place = ?"    return db.execute(query, (place,)).fetchall() print(found_at("Room 4"))print(found_at("Sita's classroom"))

Output

[('a math guide',)][('a blue water bottle',)]

The query is now fixed text, with a placeholder ? where the value goes, and the value is sent separately, in the tuple. A query written this way is called a parameterized query. It works because of the way a database runs SQL, which you saw for Python in Unit 4: first the SQL text is compiled into a plan of steps (in SQLite, into instructions for its own little virtual machine), and only then are the values bound to the placeholders, as data in memory. By the time Sita's classroom arrives, the plan is already made: “compare the place column with value number 1”. Whatever the value contains, quote marks, SQL words, anything, it can only ever be compared with places. There is no moment at which the database reads it as SQL.

That is the whole idea, and it is worth saying as a rule: keep the command and the data apart. Joining strings is fine for building a value; it is never fine for building a command.

What a placeholder can’t hold

A placeholder can stand only for a value, never for a part of the SQL itself, such as a column’s name. Suppose the board lets visitors sort the list by any column they choose. The column’s name has to go into the SQL text, so here the defense is an allow-list: a list of the only values the program accepts, checked before anything is joined.

SORTABLE = {"what", "place", "found_on"} def list_query(order_by):    if order_by not in SORTABLE:        raise ValueError("Can't sort by " + repr(order_by))    return "SELECT what, place, found_on FROM items ORDER BY " + order_by print(list_query("place"))print(list_query("found_on"))try:    print(list_query("color"))except ValueError as e:    print("Refused:", e)

Output

SELECT what, place, found_on FROM items ORDER BY placeSELECT what, place, found_on FROM items ORDER BY found_onRefused: Can't sort by 'color'

Only three exact strings, written by the programmer, can ever reach the SQL. Anything else, honest mistake or not, is refused. An allow-list (“only these”) is almost always safer than a blocklist (“anything except these”), because the programmer knows every good value, but can never list every bad one.

The same mistake in other places

Injection isn’t only about SQL. Wherever a program builds a command for another program out of text from outside, the same mistake can happen, and the fix is always to keep command and data apart:

Where The flaw The fix
SQL joining user text into a query placeholders; an allow-list for names of columns and tables
a command line joining a file name into a command run by the shell (shell=True) pass a list of arguments with no shell: subprocess.run(["resize-photo", name])
file paths joining a requested name onto a folder, so a name can point outside it check that the final, resolved path is still inside the folder
a web page joining user text into HTML escape it (next section)
an AI assistant text from a web page or a document, given to a chatbot that can act, read as instructions instead of as material to work on treat what the AI read as data: let it suggest, and have a person or a fixed rule approve any action

The last row is new, and has a name: prompt injection. A chatbot that summarizes your email reads the emails as part of its prompt; an email can contain sentences that look like instructions. Unlike SQL, a language model has no placeholders that keep instructions and data apart, which is why careful designers give an AI that reads outside text only the power to suggest, never to send, pay or delete on its own.

Try it

Find the injection

Each program builds a command from data that came from outside. Tap a line of the flaw, say what harm it could allow, then choose the fix. The fixed lines then show in the program.

staff.py: find a member of staff by name

Program 1 of 3 · right the first time: 0 of 9

1. Find a flaw: tap a line of it in the program. 1 flaw left to find.

Follow what came from outside the program, from a user, a form or a file, and see where it goes.

Why does a parameterized query stop SQL injection?

Pick an answer to see why.

A program lets the user choose which column to sort a table by. Why can’t the column name go in as a ? placeholder, and what should the program do instead?

Pick an answer to see why.

When text becomes a script: cross-site scripting

Text that the browser reads as a page

A web page is text in HTML, and the browser reads every < as the start of a tag. When a server puts something a user typed into a page, with no change, the browser can’t tell where the page’s own HTML ends and the user’s text begins. It reads it all as the page.

You can see this with no attack at all. Suppose someone reports a key that fell off a keyboard, and types its letter the way it is printed, inside angle brackets:

import html report = "The <i> key from a keyboard"print("<li>" + report + "</li>")print("<li>" + html.escape(report) + "</li>")print(html.escape('Tom & Jerry <3 "Ram\'s" pencil'))

Output

<li>The <i> key from a keyboard</li><li>The &lt;i&gt; key from a keyboard</li>Tom &amp; Jerry &lt;3 &quot;Ram&#x27;s&quot; pencil

In the first line, <i> is HTML’s tag for italics: the browser shows “The key from a keyboard”, with the letter gone and everything after it on the page in italics, the rest of the list and the footer too, for every visitor. That is a harmless tag. Text that contains a script element instead, or one of the many other ways HTML has of running JavaScript, would run that program in every visitor’s browser, as if the website itself had sent it. This flaw is cross-site scripting, usually written XSS.

What a script in someone else’s page can do

A script that runs inside a page has the page’s powers, in the visitor’s browser, with their sign-in. On the lost-and-found board, if a member of staff opens the list, such a script could:

  • send requests as that staff member, such as deleting every item, because the browser adds their session cookie by itself;
  • read anything the page shows them, and send it elsewhere;
  • change what the page shows: a fake “Your session ran out, sign in again” box that sends the password to someone else;
  • read the session cookie itself, if it isn’t marked HttpOnly (Unit 6), and then the attacker has the session.

When the text was stored (a report, a comment, a profile name), it attacks everyone who opens the page later: stored XSS. When it is only reflected back from the request (a page that says “You searched for: …” and repeats the search words), an attacker must get the victim to open a crafted link: reflected XSS. The fix is the same for both.

The fix: escape what you show

Escaping means replacing each character that has a meaning in HTML by a code that shows the character itself: < becomes &lt;, > becomes &gt;, & becomes &amp;, and quote marks become &quot; and &#x27;. That is what html.escape() did in the program above, and in Unit 9’s certificates. The browser then shows the angle brackets instead of reading them: The <i> key from a keyboard, exactly as typed.

The rules that make it work:

  • Escape when you put text into the page, every time, whatever the text is and wherever it came from: a report, a name, a value from your own database (which someone else put there), even an error message that repeats what was typed.
  • In the page’s JavaScript, use textContent, never innerHTML, for text (Unit 6 and Grade 9): the browser puts it in as text and never reads it as HTML.
  • Mind where the text goes. Escaping is right for text between tags and inside quoted attributes. A link’s address is different: an address can itself say “run this script” instead of “open this page”, so check what kind of address it is, with an allow-list (only https://). Never put user text inside a <script> element or a style.
  • Let the tools do it. Web frameworks’ page templates escape every value automatically unless told not to. The flaw comes back when a programmer switches that off “just here”, or builds HTML by joining strings, as below.

Two more defenses limit the damage if a flaw slips through anyway (defense in depth, Grade 9): session cookies marked HttpOnly, so a script can’t read them; and a Content-Security-Policy, a header in the server’s response that tells the browser which scripts the page may run (for example, only scripts from the site’s own files, none written into the page), so injected script is refused.

Try it

Find the unescaped text

Three places where text from users reaches a page. Find each flaw, say what it could allow, and fix it.

board.py: one notice on the school notice board, as HTML

Program 1 of 3 · right the first time: 0 of 9

1. Find a flaw: tap a line of it in the program. 1 flaw left to find.

Follow what came from outside the program, from a user, a form or a file, and see where it goes.

A notice board’s server puts every notice into the page with html.escape(). A notice reads: Fees due <b>Friday</b>. What does every visitor see?

Pick an answer to see why.

Which is the best fix for cross-site scripting?

Pick an answer to see why.

Storing passwords

Counting the thief’s guesses

Grade 9 showed how a careful website stores passwords: never the password, but a salted, slow hash of it. Now see why, by counting. No password is cracked here: we only count how much work a thief would have to do.

Suppose the board’s database is copied: through a flaw like the ones above, a backup on a lost pen drive, a careless insider. The thief has every user’s stored value. A hash can’t be undone, so the thief can only guess: take a likely password, hash it, and see whether the result is in the table. How long that takes depends on three numbers:

  1. How many guesses. Most people choose from a small set: common passwords, names, words, dates. When RockYou, a website that made apps for social networks, lost 32 million passwords in 2009, the 5,000 most common passwords on it were used by one user in every five (Imperva’s analysis). A list of a few million leaked passwords covers far more. Four words picked truly at random from a list of 7,776 give 7,776⁴, about 3.7 × 10¹⁵ possibilities.
  2. How much each guess costs. A fast hash such as SHA-256 is built to be quick: a gaming graphics card works out billions a second. A slow hash made for passwords (scrypt, PBKDF2, Argon2, bcrypt) is built to cost a lot, deliberately: PBKDF2 at 600,000 rounds costs at least 600,000 fast hashes per guess; scrypt also needs a block of memory for each guess, which makes graphics cards, with their thousands of small cores sharing little memory, much less of an advantage.
  3. How many users each guess covers. Without salt, every user with the same password has the same hash, so the thief hashes each guess once and looks it up among all the stored hashes at once, in a dictionary (Unit 3’s hash table: about one step, however many users). With a different random salt for each user, each guess must be hashed again for every user: the work is multiplied by the number of users.

So: work = guesses × users (if salted) × cost of each guess.

Try it

Count the thief’s work

Start with a fast hash and no salt, and 10 million leaked passwords. Then add a salt, then make the hash slow. Then try the 5,000 common passwords with the slow hash; then four random words with the fast hash, for one user.

1 second1 hour1 day1 year1,000 years1 million yearsage of theuniverseAll tried in 1 millisecond
How the website stored the passwords:
Users in the stolen table:
The users chose from:
Hashes to work out
10 million
Time to try them all
1 millisecond

No salt: each guess is hashed once and looked up among every user’s hash together, so a whole table falls as fast as one account.

What you should see

A slow hash, by hand

Here is the idea of a slow hash, in a program you can run: hash the password, then hash the result again, and again, 100,000 times. The program times one guess both ways, on whatever device you are using.

import hashlibimport time  def fast_hash(salt, password):    return hashlib.sha256((salt + password).encode()).digest()  def slow_hash(salt, password, rounds=100_000):    """For learning only: the idea of a slow hash, the same hash again and again."""    h = (salt + password).encode()    for _ in range(rounds):        h = hashlib.sha256(h).digest()    return h  guesses = ["123456", "password", "namaste", "sagarmatha", "kathmandu"]for name, hash_it in [("fast", fast_hash), ("slow", slow_hash)]:    start = time.perf_counter()    for guess in guesses:        hash_it("k7Qp2x", guess)    each = (time.perf_counter() - start) / len(guesses)    print(f"{name}: {each * 1000:.4f} ms a guess, about {1 / each:,.0f} guesses a second")

On a laptop, the fast hash manages hundreds of thousands of guesses a second even in Python, the slow one about twenty (fewer still on this page, where Python runs inside the browser, and on a phone). The user signing in waits a fraction of a second, once; the thief waits that long for every guess, for every user.

The real thing, in Python

Python’s hashlib module has scrypt. This is how a real server stores and checks a password. (The Python on this page has no scrypt, because the browser’s version of Python leaves out the library it comes from; run this one on a computer.)

passwords.py

import hashlibimport hmacimport secrets N, R, P = 2**14, 8, 5      # the cost of every guess: memory and time  def new_password(password):    """What to store when someone sets a password."""    salt = secrets.token_bytes(16)    key = hashlib.scrypt(password.encode(), salt=salt, n=N, r=R, p=P)    return f"scrypt${N}${R}${P}${salt.hex()}${key.hex()}"  def check_password(stored, typed):    """True if typed is the password that stored was made from."""    _, n, r, p, salt, key = stored.split("$")    again = hashlib.scrypt(typed.encode(), salt=bytes.fromhex(salt),                           n=int(n), r=int(r), p=int(p))    return hmac.compare_digest(again.hex(), key)  sita = new_password("tiger rice window")print(sita[:17] + "…", len(sita), "characters")print(check_password(sita, "tiger rice window"))print(check_password(sita, "tiger rice windows"))print(new_password("tiger rice window") == sita)

Output

scrypt$16384$8$5$… 178 charactersTrueFalseFalse

Read it closely; every line is there for a reason:

  • Line 5: the cost. n sets how much memory each guess needs (here 16 MiB) and p how many times the work is done; together about a tenth of a second on an ordinary computer.
  • Line 10: a new random salt for every password, from secrets, Python’s module for security (never random, which is made for games and simulations, and whose numbers can be predicted).
  • Line 12: the stored value names the method, the cost and the salt, then the hash. The salt is no secret; it is needed to check the password. Because the cost is stored too, the server can raise it in a few years, when computers are faster, and still check old passwords; it re-hashes each one the next time its owner signs in.
  • Line 20: hmac.compare_digest() compares in the same time whether the first character differs or the last, so how long the check takes tells an attacker nothing.
  • The last line: the same password, signed up twice, gives a different stored value, because the salt is different. Nobody can tell that two users chose the same password.

Try it

Find the password flaw

Three programs that handle passwords, each with one flaw. Find it, say what it could allow, and fix it.

signup.py: a new member of staff chooses a password

Program 1 of 3 · right the first time: 0 of 9

1. Find a flaw: tap a line of it in the program. 1 flaw left to find.

Follow what came from outside the program, from a user, a form or a file, and see where it goes.

Two users of a website both have the password tiger rice window. The website stores a salted, slow hash. What does a thief who steals the table see?

Pick an answer to see why.

Why does a slow hash help even though it makes every sign-in slower?

Pick an answer to see why.

Reviewing a whole system

A real review is rarely one flaw in three lines. Here is the staff part of a lost-and-found board, as a beginner might write it: sign-in, sessions, the list as HTML, and Delete. It has five flaws, one from each of the ideas so far. Find them all.

Try it

Review the staff part

Read it as a reviewer: follow everything that came from outside (a name, a password, a session ID, a report) and see where it goes. Some lines are fine; the widget tells you why.

staff.py: the lost-and-found board’s staff part

Program 1 of 1 · right the first time: 0 of 15

1. Find a flaw: tap a line of it in the program. 5 flaws left to find.

Follow what came from outside the program, from a user, a form or a file, and see where it goes.

Hint

Fixes and their trade-offs

Finding a flaw is half the job. A reviewer also explains what each fix costs, so the owner can decide:

Flaw Fix Its trade-off
a fast hash, salt ignored scrypt with each user’s salt every sign-in costs the server about a tenth of a second and 16 MiB of memory; a flood of sign-ins could slow it, so limit sign-in attempts too. Existing passwords can only be changed when each user next signs in
SQL joined from the name a placeholder none: the same speed, and names with apostrophes now work
a four-digit session ID secrets.token_urlsafe(32) none, except that every staff member is signed out once when the fix goes in
item text unescaped html.escape() users can’t add formatting; if they need it, offer a safe way of the board’s own
Delete checks nobody refuse without a staff session (401) none for honest users; add a log of deletes to deal with repudiation too

Notice which fixes are free: most flaws in code cost nothing to fix once they are seen. The expensive part is finding them, which is why teams have their code read by a second person before it goes live, and why the questions you asked in the widget, where did this come from, and where does it go?, are worth asking of every line.

Real breaches: causes and harm

A breach is any time someone gets at data or systems they shouldn’t. Investigators and regulators publish reports of the big ones, and reading them is how the whole industry learns. For each, ask three questions: what caused it, who was harmed and how, and what would have prevented it.

Breach Cause Who was harmed, and how What would have prevented it
TalkTalk, 2015 SQL injection in forgotten pages; not updated; earlier attacks missed individuals: personal and bank details exposed, scam calls after; an industry: other companies checked their own old systems; the company: a fine, lost customers, a fall in its share price knowing every system you own (an inventory); placeholders; updates; investigating the first attack
RockYou, 2009 SQL injection; passwords stored as typed individuals: passwords that opened their other accounts; everyone online: the list made password guessing better everywhere placeholders; a slow, salted hash; reviewing the code before attackers found the flaw
Twitter, 2020 social engineering by phone; staff tools that one person could use to take over any account individuals: money lost to the scam; a community: false messages from trusted accounts, with an election months away; the company and trust in it two-step login that a fake page can’t copy (a security key); fewer staff with such powerful tools, and two people to approve their use

Notice the pattern. Two of three began with a flaw in code that was well known and easy to fix; the third needed no code at all. And in every case, more than one thing went wrong before harm was done: defense in depth means that one failure shouldn’t be enough.

The Twitter breach of 2020 is best classified as:

Pick an answer to see why.

Found a flaw? Responsible disclosure and the law

Only with permission

In Grade 9 you studied Nepal’s Electronic Transactions Act, 2063. Its Section 45 makes it a crime to use a computer, a program or data without the owner’s permission, or beyond the permission given, whether or not anything is changed, with up to three years in prison, a fine of up to Rs 2 lakh, or both Grade 9: Crimes involving computers. Damaging data is Section 46. “Just testing” a website you don’t own, to see whether a flaw is there, is using it beyond the permission given, however good the intention. Other countries’ laws say the same.

So security testing is done only with the owner’s written permission, saying exactly what may be tested, how, and when. Companies hire penetration testers (“pen testers”) to try to break into their own systems under such an agreement; the agreement is what makes it a job and not a crime. The only systems you may test freely are your own: the app you build in the project below, on your own computer.

If you find one by accident

Sometimes a flaw just shows itself: a page shows someone else’s marks; a link opens a folder of other people’s files. What you do next matters:

  1. Stop. Don’t look further, don’t try it again to be sure, don’t download or keep anything, don’t show your friends.
  2. Write down what you saw: the address, the time, what appeared, what you did just before.
  3. Tell the owner, privately. Look for a security contact: many sites list one in a file at /.well-known/security.txt (a standard since 2022) or on a page called “Security” or “Report a vulnerability”. For a school or government site, tell the office that runs it; a teacher can help you find who that is.
  4. Give them time to fix it, and don’t make it public until they have.
  5. Never ask for money in return for keeping quiet: that is blackmail, a crime of its own.

This is responsible disclosure, also called coordinated vulnerability disclosure: the finder and the owner agree on when the flaw is fixed and when it can be described, so that users are protected before attackers learn of it. Many companies go further and run a bug bounty: a public invitation, with written rules (which systems, which kinds of testing, what is off limits), promising not to take legal action against those who follow the rules and paying a reward for each real flaw reported. Inside a bug bounty’s rules, testing is permitted, because the owner has given permission in writing; outside them, it isn’t.

A smiling man with short dark hair, in a red T-shirt, in front of a brick wall
Figure 7.2 Dan Kaminsky, who found a flaw that affected nearly the whole internet in 2008, and worked in secret with the makers of DNS software to fix it on one day. Photo: Dan_Kaminsky.jpg : Dave Bullock derivative work: Emdee, CC BY 2.0

While using your school’s results website, you notice that changing one number in the address shows another student’s marks. What should you do?

Pick an answer to see why.

Project: secure your own app

Work in pairs, on the lost-and-found board you built in Unit 6 (or any small web app you have built yourselves). You are its owners, so you have permission to test it, on your own computer only.

  1. Threat model. Draw its trust boundary, as in Figure 7.1. List its assets, who might attack, and its entry points. Go through STRIDE for each entry point. Write a table of threats, defenses and their costs, and choose the three most important.
  2. Review. Swap code with another pair. Read theirs line by line, following everything that comes from outside: where does it enter, where does it go? List every flaw you find, with the line, what harm it could allow (in words, never as an attack), and the fix. Give the list back.
  3. Fix. Fix the flaws found in your own code. For each, write one sentence on its trade-off.
  4. Passwords. Add the staff sign-in, with new_password() and check_password() from this unit, and a session ID from secrets. Check that two staff with the same password get different stored values.
  5. Test the defenses, honestly. Report an item at a place with an apostrophe (Sita's classroom) and one whose description contains angle brackets (The <i> key). Both should be saved and shown exactly as typed. Send a Delete without a session cookie (with ask.py from Unit 6): it should be refused with 401.
  6. Present your threat model, one flaw the other pair found in your code and how you fixed it, and the defense you chose not to add, with your reason.

With only a phone: do steps 1, 2 and 3 on paper with the programs in this unit and in Unit 6, and the review in the widget above; for step 4, read passwords.py and explain each line to your partner.

In short

Exercise

Which of these is a physical security measure?

Pick an answer to see why.

Someone signs into the board as the office assistant with a password they guessed. Which STRIDE threat is that?

Pick an answer to see why.

A program runs db.execute("SELECT * FROM items WHERE id = " + item_id), where item_id came from the address. What is the best fix?

Pick an answer to see why.

Without salt, why does a thief’s work not grow with the number of users?

Pick an answer to see why.

Which is an example of responsible disclosure?

Pick an answer to see why.

a.

Write the four questions of a threat model. Answer them briefly for a school library’s app where students search for books and librarians mark books as borrowed.

Answer
b.

Explain, in your own words, why "... WHERE place = '" + place + "'" is a flaw even if nobody ever attacks the website. Then explain why "... WHERE place = ?" with the value passed separately fixes it.

Answer
c.

A web page shows visitors’ comments. Explain what cross-site scripting is, what an attacker could do with it on a site where teachers sign in, and two ways to prevent it.

Answer
d.

A website stores passwords as hashlib.sha256(password.encode()).hexdigest(). Give two weaknesses, and write what it should store instead.

Answer
e.

A thief has a table of 1,000 users stored with a salted, slow hash costing 600,000 fast hashes a guess, and can work out 10¹⁰ fast hashes a second. Work out how long it takes to try a list of 1 million passwords against every user. Then work out how long it would take if the hash were fast and unsalted.

Answer
f.

Here is part of a lost-and-found board. Find three flaws, say what harm each could allow, and write the fixed lines.

import hashlib, random def item_page(item):    return "<h2>" + item["what"] + "</h2><p>Found at " + item["place"] + "</p>" def sign_in_token():    return str(random.randint(100000, 999999)) def mark_collected(item_id, session):    db.execute("UPDATE items SET collected = 1 WHERE id = " + str(item_id))
Answer
g.

Choose one of the breaches in this unit (TalkTalk, RockYou or Twitter). Classify it: its cause, who was harmed (individuals, an industry, a community, a government) and how, and two things that would have prevented it.

Answer
h.

Your friend finds that the website of a shop in your town shows every customer’s phone number if you add a word to its address. He wants to download the list “to prove it”, then post about it. What does the law say, and what should he do instead?

Answer
i.

For each defense, name who pays its price (users, data or devices) and what the price is: a. signing users out after 10 minutes idle; b. encrypting every laptop’s disk; c. hiding a lost phone’s description on the lost-and-found board; d. two-step login.

Answer