R17 · Week 17 Recall & Drill
Week 17 revision: resource authorization, password KDFs, certificate trust and SAN checks, SQL injection boundaries, leakage-safe evaluation and least-squares gradients.
Original publication date unrecordedUpdated
Lesson preparation & details
Level: intermediate
By the end, you should be able to
- Distinguish repeated credential validation from per-resource authorization and design safe session/token revocation.
- Explain unique password salts, KDF cost and the limits of a local timing experiment.
- Validate certificate trust and SAN identity separately and diagnose four certificate failures.
- Explain injection boundaries and apply STRIDE to a password-reset design.
- State supervised feature/target shapes and preserve train/validation/test information boundaries.
- Derive least-squares gradients, rank-deficient solutions and learning-rate behavior.
Bring with you
Sources checked: . A source check does not change the publication or update date.
Editorial review: · What review means
In this article · 17 sections
Assessment scope
Security and evaluation both depend on maintaining a boundary, but not the same boundary. This revision reconstructs authentication, authorization, TLS/PKI and injection, then switches to supervised-learning splits and least squares. The worked programs run on CPU with Python, NumPy and a local OpenSSL executable. They create only in-memory data or a temporary synthetic certificate chain; they never attack a service or use a real password. Timing is an observation of this machine, not an attacker benchmark.
Sessions covered
- S081 — AuthN vs AuthZ, Sessions & Password Storage
- S082 — TLS 1.3, PKI & Cert Lifecycle
- S083 — OWASP Top 10, Secrets Mgmt & Threat Modelling
- S084 — The ML Mental Model — Features, Labels, Train/Val/Test
- S085 — Linear Regression from Scratch (numpy)
90-min structure
| Block | Minutes | Work |
|---|---|---|
| Warm-up | 5 | State the five boundaries without notes. |
| Reconstruction | 30 | Answer each numbered question before its solution. |
| Drill | 30 | Predict the KDF, certificate, SQL and split results. |
| Quiz | 15 | Explain the failure cases and password-reset design. |
| Gap analysis | 10 | Select one unseen retest. |
These are pacing suggestions, not a claim that certificate generation and every extension fit every learner's 90 minutes.
Blank-page reconstruction · 30 min
S081 · AuthN vs AuthZ
- Define both terms; which checks occur at login and on later requests?
- Why salt passwords and use a purpose-built password hash?
- Compare a server-side session with a self-contained token, including revocation.
Solutions. Authentication establishes confidence in an identity. Authorization decides whether that principal may perform this action on this resource in this context. A login may establish a session, but each request still validates its session credential or token, and sensitive actions may require fresh authentication. A request for /invoices/42 must check the invoice's tenant and ownership, not merely that the caller logged in. Deny by default and enforce the rule server-side on every protected access, including background and internal paths.
A random per-password salt prevents equal passwords from sharing the same stored result and prevents one precomputed table from serving all accounts. It is stored with the hash, not kept secret. A work-factor-controlled KDF makes each guess expensive; Argon2id additionally imposes memory cost. OWASP's captured password-storage guidance recommends Argon2id where available, with alternatives appropriate to platform/compliance constraints. PBKDF2 below is a standard-library teaching fixture, not a claim that its smallest iteration count is a deployment recommendation. Tune work within measured login latency and denial-of-service limits, version the stored parameters, and upgrade them on successful verification.
A session identifier is an opaque reference to server state, so deleting that state can revoke access centrally. A signed token can carry claims verified locally, but a valid signature neither guarantees current authorization nor revokes the token. Short access-token lifetimes bound exposure; refresh-token rotation/revocation controls future issuance, not already-issued access tokens. Immediate revocation can require a denylist, session/version lookup or online introspection. These trade some offline benefits for freshness; they do not make tokens categorically useless.
S082 · TLS & PKI
- Name three channel properties and something TLS does not establish.
- Explain leaf, intermediate, trust anchor and hostname validation.
- Diagnose expiry, name mismatch, missing intermediate and untrusted root.
Solutions. TLS 1.3 provides authenticated, confidential, integrity-protected communication under its negotiated mode and trust assumptions. In the ordinary certificate-based web case the client authenticates the server's configured identity; client authentication is optional. A domain-validated certificate is not proof of an honest operator, safe application or protected data at rest. TLS also does not hide all traffic metadata.
A leaf certificate binds a public key to identifiers. An intermediate signs the leaf; the client builds and validates a path to a locally trusted root, checking signatures, validity, CA constraints and applicable policy. Merely receiving a root from a server does not make it trusted. Name validation is a separate check against the reference identity; RFC 9525 uses subjectAltName identifiers and no Common Name fallback. Legacy clients can differ; the lab uses an explicit hostname-verification option rather than treating SNI as verification.
| Symptom | Diagnosis and repair |
|---|---|
| Expired/not-yet-valid | Check certificate validity and client clock; renew/deploy the correct chain. |
| Host mismatch | Compare the requested DNS name with SAN; do not disable verification. |
| Unable to get issuer | Often a missing intermediate; configure the server's intermediate chain and inspect the actual path. |
| Unknown/self-signed root | The path does not end at an authorized trust anchor; use the intended CA or explicitly provision a private root through policy. |
Automate renewal and deployment, then monitor the certificate actually served on every endpoint. Successful renewal without reload is not successful rotation. The synthetic chain below demonstrates separate trust and name checks; it is not a live-service availability measurement or a complete revocation-policy test.
S083 · OWASP & Threat Modelling
- Which edition puts Broken Access Control first, and what does that ranking mean?
- What prevents injection, and why is a generic blacklist insufficient?
- Apply all six STRIDE categories to a password-reset endpoint.
Solutions. In the explicitly pinned OWASP Top 10:2021, A01 is Broken Access Control, moved from fifth in 2017; the project's category evidence motivates that prioritization, not a universal ordering for every application. Do not call a dated list “the current list” indefinitely. Authentication without per-object authorization is one route to this failure.
For SQL values, bind parameters rather than concatenate input into grammar. Parameters do not stand for arbitrary column names or sort directions: select those from fixed authorized choices. Use subprocess argument arrays without a shell where possible, context-specific output encoding for HTML/attribute/URL contexts, and a maintained HTML sanitizer when deliberately accepting rich markup. Input validation remains useful for business rules, but deleting quotes is neither a general injection defense nor harmless to names such as O'Brien. Secret management adds a different boundary: use least-privilege service identities and a secret store, redact logs, rotate exposed values and audit access; a vault cannot compensate for an overprivileged application.
For SSRF, validate permitted schemes/destinations, account for DNS resolution, redirects and private/link-local addresses, and constrain egress at the network layer. A substring denylist is insufficient. Metadata protections such as AWS IMDSv2 session tokens and hop limits are useful provider-specific controls, not a universal “metadata authentication fixes SSRF” rule. Never let retrieved URLs grant an application broader network access than its task needs.
Password-reset worked threat model: the asset is control of an account; the trust boundaries are unauthenticated request → reset issuer, mailbox → holder of token, and token redemption → credential/session store.
- Spoofing: generate a high-entropy, short-lived, single-use token; email only a reset link, not a new password. Possession proves access to that channel, not every aspect of a person's identity.
- Tampering: store a digest of the opaque token associated with one account, expiry and purpose; atomically check and consume it while changing the password. Two concurrent redemptions must not both succeed.
- Repudiation: record issue/redemption outcomes, time and safe correlation IDs; never log raw tokens/passwords. Protect logs and define retention.
- Information disclosure: give a uniform response for known/unknown accounts and avoid obvious timing enumeration; HTTPS, restrictive referrer handling and no third-party token-leaking page assets matter too.
- Denial of service: rate-limit by account and source with abuse monitoring; do not lock an account just because someone requested a reset.
- Elevation of privilege: bind account/action server-side, do not accept a supplied role or account override, and invalidate existing sessions/refresh grants according to the recovery policy. Do not automatically log in a caller merely because they requested a reset.
S084 · ML Mental Model
- State feature/target shapes for supervised tabular learning and the limits of that picture.
- Why separate train, validation and test?
- Distinguish the optimized loss from the metric humans use.
Solutions. With n observations and d numeric features, X has shape (n,d) and a scalar target y has shape (n,). The model maps each feature row to a prediction. This is not literally every dataset: images/sequences have additional axes, multi-output targets can be matrices, and unsupervised learning need not have y. Flattening is a representation choice, not a theorem about data.
Training fits weights and all learned preprocessing. Validation selects features, hyperparameters and decision thresholds. An untouched test estimates the frozen procedure on a specified deployment-like distribution. Time or entity groups may determine the split; a random row split can leak future or repeated-subject information. A final score drop could reflect leakage, distribution shift, sampling noise or deployment bugs—one symptom does not identify the cause.
A differentiable squared loss or log loss guides optimization; RMSE, precision, recall, calibration and business cost grade different properties. For 10 positives among 1,000 cases, predicting all negative gives 99% accuracy and zero recall. Accuracy is mathematically valid but insufficient for that alerting objective. State prevalence, confusion counts and decision costs; precision-recall curves are not a replacement for an explicit operating point.
S085 · Linear Regression
- Derive the normal equations and gradient with an explicit loss normalization.
- What happens when columns are dependent?
- When prefer iterative optimization, and how does step size fail?
Solutions. Include a column of ones in X for an intercept. For
setting the gradient to zero gives X.T @ X @ theta = X.T @ y. If X has full column rank, the unique solution is (X.T X)^(-1) X.T y; use a QR/SVD-based least-squares solver rather than explicitly computing an inverse. With dependent columns, minimizers need not be unique; the pseudoinverse selects the minimum-norm one. Ridge adds a positive penalty; penalizing all coordinates makes X.T X + lambda I positive definite for lambda > 0. If the intercept is exempt, analyze the actual penalty matrix rather than asserting every regularized matrix is invertible.
Linear means linear in fitted coefficients: a + b*x + c*x*x is linear regression on transformed features. Iteration is useful for large/sparse or streaming data where a dense normal-equation matrix is expensive. On a quadratic with largest Hessian eigenvalue Lh, fixed-step gradient descent converges in positive-curvature directions for 0 < eta < 2/Lh. A very small eta crawls; near the boundary errors can alternate and decay slowly; beyond it they diverge. At the boundary one direction can oscillate without decreasing. Feature scaling changes conditioning, not the need to check the step size.
One-direction worked answer: for L(theta) = theta²/2, the minimizer is zero, the Hessian eigenvalue is 1, and each update multiplies the error by 1-eta. Starting at 1, eta=0.1 gives 1, 0.9, 0.81 (slow decay); eta=1.5 gives 1, -0.5, 0.25 (alternating decay); eta=2 gives 1, -1, 1 (no decay); eta=2.1 gives 1, -1.1, 1.21 (divergence). A negative multiplier alone is therefore not divergence. For a rank-deficient least-squares problem, zero-curvature directions remain at their initial values: gradient descent from zero can reach the minimum-norm solution under the stated step condition, but arbitrary initialization can retain a null-space component. The pseudoinverse statement above does not imply every iterative run selects that same minimizer.
Hands-on drill · 30 min
Run the Python fences in order in one local process. Inputs are synthetic. The certificate fixture requires cryptography and OpenSSL already available; its synthetic private keys remain in process memory; only public certificates are written to a temporary directory. No network TLS handshake is performed.
Step 1 — slow on purpose (7 min)
import hashlib, hmac, time
password = b"teaching-only-not-a-real-password"
salt_a, salt_b = bytes(range(16)), bytes(range(16, 32))
t0 = time.perf_counter()
for _ in range(10_000):
hashlib.sha256(password).digest()
fast_seconds = time.perf_counter() - t0
print("10000 SHA256 operations seconds", round(fast_seconds, 6))
for rounds in (10_000, 100_000, 600_000):
t0 = time.perf_counter()
digest = hashlib.pbkdf2_hmac("sha256", password, salt_a, rounds)
elapsed = time.perf_counter() - t0
assert hmac.compare_digest(digest, hashlib.pbkdf2_hmac("sha256", password, salt_a, rounds))
assert digest != hashlib.pbkdf2_hmac("sha256", password, salt_b, rounds)
print("PBKDF2 rounds", rounds, "local derivation ms", round(1000*elapsed, 3))The fixed salts make the demonstration reproducible; production salts must be independently random. The test verifies repeatability and separation by salt. Timing one CPU call cannot estimate a GPU attacker's throughput, login usability under load or an acceptable production policy. Measure repeated trials and service concurrency before selecting a work factor.
Step 2 — read a certificate chain (8 min)
import datetime as dt, tempfile, subprocess
from pathlib import Path
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
now = dt.datetime.now(dt.timezone.utc)
def make_key():
return rsa.generate_private_key(public_exponent=65537, key_size=2048)
def name(text):
return x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, text)])
def certificate(subject, issuer, pub, signer, serial, ca=False, san=None, expired=False):
builder = (x509.CertificateBuilder().subject_name(name(subject)).issuer_name(name(issuer))
.public_key(pub).serial_number(serial)
.not_valid_before(now-dt.timedelta(days=2))
.not_valid_after(now+dt.timedelta(days=-1 if expired else 30))
.add_extension(x509.BasicConstraints(ca=ca, path_length=1 if ca else None), critical=True)
.add_extension(x509.KeyUsage(digital_signature=True, content_commitment=False,
key_encipherment=not ca, data_encipherment=False, key_agreement=False,
key_cert_sign=ca, crl_sign=ca, encipher_only=False, decipher_only=False), critical=True))
if san:
builder = builder.add_extension(x509.SubjectAlternativeName([x509.DNSName(san)]), False)
return builder.sign(signer, hashes.SHA256())
rk, ik, lk, uk = [make_key() for _ in range(4)]
root = certificate("Lab Root", "Lab Root", rk.public_key(), rk, 1, ca=True)
inter = certificate("Lab Intermediate", "Lab Root", ik.public_key(), rk, 2, ca=True)
leaf = certificate("ignored-cn.example", "Lab Intermediate", lk.public_key(), ik, 3, san="service.test")
expired = certificate("ignored-cn.example", "Lab Intermediate", lk.public_key(), ik, 4, san="service.test", expired=True)
untrusted = certificate("Other Root", "Other Root", uk.public_key(), uk, 5, ca=True)
with tempfile.TemporaryDirectory(prefix="w17-cert-") as tmp:
p = Path(tmp)
for label, cert in {"root":root,"inter":inter,"leaf":leaf,"expired":expired,"other":untrusted}.items():
(p/f"{label}.pem").write_bytes(cert.public_bytes(serialization.Encoding.PEM))
def verify(label, hostname="service.test", trust="root", chain=True):
args = ["openssl","verify","-no-CApath","-CAfile",str(p/f"{trust}.pem"),"-verify_hostname",hostname]
if chain: args += ["-untrusted",str(p/"inter.pem")]
result = subprocess.run(args+[str(p/f"{label}.pem")],capture_output=True,text=True)
print(label,hostname,trust,chain,"exit",result.returncode,(result.stdout+result.stderr).strip())
return result.returncode
assert verify("leaf") == 0
assert verify("leaf", hostname="wrong.test") != 0
assert verify("expired") != 0
assert verify("leaf", chain=False) != 0
assert verify("leaf", trust="other") != 0
print("SAN",leaf.extensions.get_extension_for_class(x509.SubjectAlternativeName).value)
print("subject",leaf.subject.rfc4514_string(),"issuer",leaf.issuer.rfc4514_string())
print("synthetic lifetime days",(leaf.not_valid_after_utc-now).days)The valid path succeeds although the leaf Common Name differs; wrong SAN reference, expired leaf, missing intermediate and wrong root each fail. That isolates the four diagnoses without pinning a public IP or suppressing verification errors. A live investigation would inspect the endpoint's served chain and reference hostname with verification enabled. The old command's -servername selected SNI but did not itself request a hostname check; displaying the first PEM also did not inspect every certificate in the chain.
Step 3 — injection is a parsing problem (8 min)
import sqlite3
con = sqlite3.connect(":memory:")
con.executescript("CREATE TABLE users(id INTEGER PRIMARY KEY,name TEXT,is_admin INTEGER);"
"INSERT INTO users VALUES(1,'ada',0),(2,'root',1);")
con.execute("INSERT INTO users VALUES(3,?,0)", ("O'Brien",))
def vulnerable(value):
return con.execute(f"SELECT name FROM users WHERE name = '{value}' ORDER BY id").fetchall()
def stripped(value):
return vulnerable(value.replace("'", "").replace(";", "").replace("--", ""))
def bound(value):
return con.execute("SELECT name FROM users WHERE name=? ORDER BY id",(value,)).fetchall()
payload = "ada' OR '1'='1"
assert vulnerable(payload) == [('ada',),('root',),("O'Brien",)]
assert bound(payload) == []
assert stripped('ada" OR 1=1 OR "x"="x') == [] # NOT a bypass of this quoted query
assert stripped("O'Brien") == [] # valid data is corrupted
assert bound("O'Brien") == [("O'Brien",)]
assert bound("ada") == [('ada',)]
print("SQL: injection reproduced; binding preserves data; claimed no-quote bypass rejected")The double-quote payload stays inside a single-quoted SQL literal; it never bypassed this particular sanitizer. Correcting that observation does not make a quote-stripping design sound: it changes legitimate names and is context-specific. Binding values fixes the demonstrated grammar boundary without promising to solve authorization or arbitrary dynamic SQL identifiers.
Step 4 — manufacture a leaked score (7 min)
import numpy as np
rng = np.random.default_rng(0)
n, d = 200, 500
X = rng.normal(size=(n,d)); y = rng.integers(0,2,n)
order = rng.permutation(n)
folds = [order[f::5] for f in range(5)]
def select(rows):
centered = X[rows]-X[rows].mean(0)
target = y[rows]-y[rows].mean()
denom = np.linalg.norm(centered,axis=0)*np.linalg.norm(target)
corr = np.divide(abs(centered.T@target),denom,out=np.zeros(d),where=denom>0)
return np.argsort(corr,kind="stable")[-20:]
def evaluate(leak):
scores=[]; fitted_rows=[]
for test in folds:
train=np.setdiff1d(np.arange(n),test)
used=np.arange(n) if leak else train
chosen=select(used); fitted_rows.append(set(used))
A=np.c_[np.ones(len(train)),X[np.ix_(train,chosen)]]
theta=np.linalg.lstsq(A,y[train],rcond=None)[0]
pred=np.c_[np.ones(len(test)),X[np.ix_(test,chosen)]]@theta >= .5
scores.append(float(np.mean(pred==y[test])))
if not leak: assert set(used).isdisjoint(test)
return float(np.mean(scores)),fitted_rows
leaked, lr = evaluate(True); honest, hr = evaluate(False)
assert all(set(test) <= used for test,used in zip(folds,lr))
assert all(set(test).isdisjoint(used) for test,used in zip(folds,hr))
print("same-fold noise experiment leaky/honest",round(leaked,3),round(honest,3))
# Least-squares and gradient checks independent of the classifier fixture.
A=np.c_[np.ones(4),np.arange(4.)]; target=np.array([1.,3.,5.,7.])
theta=np.linalg.lstsq(A,target,rcond=None)[0]
assert np.allclose(theta,[1,2])
w=np.array([.3,.7]); loss=lambda z: np.sum((A@z-target)**2)/(2*len(A))
grad=A.T@(A@w-target)/len(A); eps=1e-6
numeric=np.array([(loss(w+eps*e)-loss(w-eps*e))/(2*eps) for e in np.eye(2)])
assert np.allclose(grad,numeric,rtol=1e-6,atol=1e-8)
rank_def=np.c_[A,A[:,1]]
minimum_norm=np.linalg.lstsq(rank_def,target,rcond=None)[0]
assert np.allclose(minimum_norm,[1,1,1])
print("least squares",theta,"rank-deficient minimum norm",minimum_norm)Both variants use exactly the same folds. The leaky selector sees test labels; the honest selector does not. The source-backed correctness rule is the information boundary, not an assertion that every random fixture must show a positive score gap. There is no signal by construction; selection on all labels can manufacture apparent generalization. Scaling, imputation and other learned transforms belong inside the split even without labels.
Answer constraints and corrections
Misconception to reject: “I strip dangerous characters at the boundary, so downstream interpreters are safe.” Different interpreters have different grammars; a generic filter cannot replace parameter binding, shell-free invocation or output-context encoding. Validation and carefully specified sanitization still have legitimate jobs. Keep identity, authorization, transport trust and parsing as separate controls.
Recall answers:
- Authentication is not “once forever”: validate credentials/session state at the appropriate request boundary, reauthenticate when needed, and authorize the particular object and action every time.
- Fast digests permit cheap offline guesses. Unique salts prevent cross-account precomputation reuse; tunable KDF cost raises per-guess work without claiming an imperceptible user cost.
- TLS protects channel confidentiality/integrity and authenticates the configured peer under trust assumptions, not its honesty or application security.
- A good validation score and bad production score call for a leakage/split/shift/implementation investigation, not a single-cause verdict. Reproduce the train-only pipeline with deployment-like splits.
- The six-category reset design above is the stretch answer; atomic one-use redemption and session invalidation are as important as token entropy. Verify failure cases, not just the happy path.
Gap analysis + next week preview · 10 min
Retest one failure you can explain concretely: a valid SAN with a misleading Common Name; an O'Brien lookup; a foreign-tenant invoice; or a selector that sees test labels despite fitting regression only on training rows. Record prediction, actual result and the corrected rule. The next five sessions cover logistic regression, regularization, bias/variance, trees and bagging; the split discipline and gradient normalization here are their prerequisites.
Corrective source notes
Primary references checked from saved snapshots on 2026-10-07: OWASP authorization, password storage, password reset, SQL injection prevention, Top 10:2021 A01, STRIDE, SSRF prevention, TLS 1.3 RFC 8446, service identity RFC 9525, scikit-learn 1.7 leakage and linear models. These support the named mechanisms, not service penetration testing or a deployed ML performance claim.
Part of the 6-month evergreen learning plan.
Pause / Recall / Apply
Can you explain it without the page?
Close the example. Reconstruct the core idea, then change one assumption. Mark complete when you’re ready; you can always undo it.
Stored in this browser only. No account, no sync. Clearing browser data removes your record.