The previous two lessons were an inventory of the problem: the technical attacks ordered by phase of the chain, and the human vector with all its variations. This lesson is the inventory of the response. We are going to work through the catalogue of defences that counters what you have seen, organised by the layers of defence in depth you established in 01-03: perimeter and network, endpoint, data, application and development life cycle, and logging and observability. For each measure you will see what it does, when it is used and which specific attack it neutralises; the detailed implementation belongs to module 5 and is only linked to here. And because Nimbus's budget is not infinite, the lesson ends with the most useful thing of all: a table of the twelve highest-return measures with their cost, effort and impact, and a phased 30, 90 and 180-day plan that Marta could approve on Monday.
Contents
- How to choose defences: three criteria before spending a euro
- Perimeter and network protection
- Endpoint protection
- Data protection
- Protecting the application and the development life cycle
- Logging and observability as a protection measure
- Prioritising on an SME budget: the 12 highest-return measures
- Phased plan: 30, 90 and 180 days
- How to choose defences: three criteria before spending a euro
Before the catalogue, the method. The most common way of spending badly on security is buying what is advertised rather than what is missing.
Criterion 1: which specific attack does it neutralise? Every measure must be traceable to an entry in the summary table of 02-02 or in the catalogue of 02-03. If you do not know which attack it stops, you do not know whether you need it.
Criterion 2: which phase of the chain does it act on, and what other layers do I already have there? Remember the Kill Chain from 02-01: if all your measures act on the same phase, you have a defence that is thick on one side and bare on the other.
Criterion 3: what type of control is it? Controls are classified by function, and a healthy defence has all four types:
| Type | What it does | Example at Nimbus |
|---|---|---|
| Preventive | Stops it happening | MFA, parameterised queries, firewall |
| Detective | Warns that it is happening | Mass export alert, audit log |
| Corrective | Reduces the damage or restores | Restoring backups, revoking tokens |
| Deterrent | Discourages the attempt | Legal banner, notice that the session is recorded |
The typical imbalance you already diagnosed with the NIST CSF in 02-01: almost everything preventive, almost nothing detective. It is comfortable because preventive controls are bought and installed once, whereas detective ones require somebody to look. But when prevention fails — and it will — the only thing that limits the damage is detection.
A fourth criterion, informal but decisive in an SME: does this measure survive a bad day? A defence that demands ten minutes of daily discipline from somebody who is already swamped will not last three weeks. The measures that work at Nimbus are the ones that are on by default, automated or in the natural path of the work.
- Perimeter and network protection
The classic perimeter has lost weight — half the headcount works remotely and the data is in the cloud — but it has not disappeared: it is still the first line against the mass scanning of 02-02.
2.1 Firewalls: which type is needed and what for
| Type | What it inspects | What it stops | Where it fits at Nimbus |
|---|---|---|---|
| Packet filtering | IP, port, protocol | Access to services that should not be reachable | The cloud provider's security groups; it is what was missing on the 0.0.0.0/0 for port 22 |
| Stateful | The above + the state of the connection | Packets that do not belong to an established connection | Standard in any modern firewall |
| Next-generation (NGFW) | Application and identity, not just port | Legitimate traffic over port 443 but towards inappropriate destinations | The Valencia office router |
| Web application (WAF) | HTTP requests: body, parameters, headers | SQL injection, XSS, path traversal, bots | In front of the API (A-04) |
On the WAF, precisely, because it is the most misunderstood measure of all: a WAF buys time, it does not fix code. It is extraordinarily useful for three things: holding back the automated background noise, applying a virtual patch while a newly discovered vulnerability is being fixed, and providing visibility of what is being attempted against the API. What it does not do: replace parameterised queries or detect an IDOR, because an IDOR request is perfectly well formed. If Nimbus installs a WAF and relaxes code review as a result, it has made things worse.
2.2 Segmentation
What it is. Dividing the network into zones with control between them, so that compromising one does not give access to the others.
flowchart TB
INT["INTERNET"] --> DMZ["PUBLIC ZONE\nLoad balancer + WAF"]
DMZ --> APP["APPLICATION ZONE\nAPI, workers\nNo direct access from the Internet"]
APP --> DAT["DATA ZONE\nPostgreSQL, Redis\nReachable only from the application zone"]
OFI["VALENCIA OFFICE\nCorporate wifi"] -->|"only via bastion + MFA"| APP
INV["GUEST WIFI\nIsolated, no internal access"] --> INT
CONS["EXTERNAL CONSULTANCY"] -->|"just-in-time access, with expiry"| APP
Why it is the most cost-effective measure against ransomware. The timeline in 02-02 showed the attacker spending days moving laterally. Segmentation is what turns "they got into a laptop" into "they got into a laptop" rather than "they encrypted the whole infrastructure". Three practical rules for Nimbus:
- The guest wifi touches nothing internal. It is a five-minute control on the router and it removes a whole class of risk.
- The data zone only accepts connections from the application zone. Never from the office and never from the Internet. You already checked this with
describe-security-groupsin 01-04. - Administrative access always goes through a single point (a bastion host or a VPN with MFA), which is then the only place that really needs watching.
2.3 Anti-DDoS protection
Against the volumetric kind, the defence is bought in: the cloud provider or a CDN absorbs the traffic before it reaches Nimbus's link. It is one of the few defences that genuinely cannot be built with your own resources, because it requires network capacity.
Against the application-layer kind, the defence is programmed, and you already saw it in 02-02: mandatory pagination, business range limits, rate limiting and a maximum query time. No anti-DDoS service tells an expensive request from a cheap one: to it, both are a legitimate HTTPS request.
2.4 VPN and secure remote access
With 19 people outside the office, remote access is Nimbus's perimeter.
| Model | How it works | Advantage | Problem |
|---|---|---|---|
| Traditional VPN | The machine joins the internal network and reaches everything | Simple and familiar | "All or nothing" access: one compromised credential gives away the whole network. It is the Colonial Pipeline case we will see in 02-06 |
| Bastion / jump host | A single jump point with MFA and logging | One point to watch and audit | Requires discipline in use |
| Zero Trust access per application | Authorisation is granted application by application, verifying identity and device state on every request | There is no "inside"; a compromise does not hand over the network | Higher initial configuration cost |
Recommendation for Nimbus: mandatory MFA on remote access (no exceptions, including the consultancy), a bastion host with session logging for administrative access, and just-in-time access with an expiry for third parties instead of the current permanent access of asset A-19. The identity detail goes in 02-05; the network configuration, in 05-04.
- Endpoint protection
The 40 laptops (A-14) are where people work, and therefore where the phishing of 02-03 lands.
3.1 Antivirus versus EDR
| Aspect | Traditional antivirus | EDR (endpoint detection and response) |
|---|---|---|
| Detects by | Signatures of known files | Behaviour: what a process does, who it talks to, what it modifies |
| Covers | Known malware | Fileless attacks, misuse of legitimate tools, lateral movement |
| Provides | Blocking | Blocking + telemetry and history for investigation |
| Allows | — | Remotely isolating a compromised machine |
| Cost | Low | Medium; it requires somebody to attend to the alerts |
Why the difference matters in the Nimbus case. Look back at exercise 3 of 02-03: Rubén's laptop runs a component that establishes an outbound channel and steals the browser cookie. A signature-based antivirus may see nothing, because the file is new and because much of the activity uses legitimate system tools. An EDR sees the pattern: an office process launching a command interpreter, an access to the browser's credential store, an outbound connection to a recently registered domain. And, above all, it allows the machine to be isolated with one click and lets you reconstruct afterwards what happened.
The realistic warning: an EDR whose alerts nobody looks at is an expense, not a defence. Before buying it, you have to decide who attends to it and when. For Nimbus, the sensible option is usually an EDR with a managed monitoring service, or a well-configured EDR with few, highly actionable alerts.
3.2 The other three endpoint measures
| Measure | Which attack it stops | Practical detail for Nimbus |
|---|---|---|
| Full disk encryption | Theft or physical loss of a laptop | Enabled on all 40 machines, with centralised custody of the recovery keys. Without custody, an employee who forgets their key causes a self-inflicted data loss |
| Patch management | Exploitation of known vulnerabilities — the vector of WannaCry and Equifax (02-06) | Automatic operating system and browser updates; a committed deadline for critical ones (7 days is a reasonable target); an inventory that shows which version each machine has |
| Application control | Execution of unauthorised software, including the baiting USB stick | Allow only approved applications; block macros from external sources; block execution from temporary folders and from removable devices |
The most underrated of the three is patching. It is not glamorous, it cannot be shown to a customer and it appears in no sales brochure. But two of the biggest incidents in recent history — the ones we will analyse in 02-06 — were known vulnerabilities with a patch available for months. Hardening and endpoint management are studied in detail in 05-06.
- Data protection
The previous layers protect containers. This one protects what is inside, and it is the only one that still helps when the others fail.
4.1 Classification: the prerequisite
You cannot protect differently what has not been classified. You already did it in 01-04 with the four levels (public, internal, confidential, restricted) and their three rules: inheritance upwards, aggregation raising the level, and context defining sensitivity. Classification is not a measure in itself: it is what makes it possible to apply everything else without going bankrupt.
4.2 Encryption in transit and at rest
| Where | What it protects | State at Nimbus |
|---|---|---|
| In transit, external | Eavesdropping between the client and the API | TLS 1.3 with HSTS: done |
| In transit, internal | Eavesdropping on the private network (flow F3 of the DFD) | Outstanding: TLS between the load balancer and the API as well |
| At rest, database | Access to the disk or the volume | Volume encryption with managed keys: done |
| At rest, bucket | Access to the object storage | Server-side encryption with a managed key: done |
| At rest, backups | Theft of a backup | Critical: the backup contains everything. Encrypted and with a key different from production's |
| At field level | Legitimate database access that should not see certain fields | Worth considering for the most sensitive fields |
Two clarifications that prevent false senses of security:
- The provider's encryption at rest protects against theft of the physical medium, not against an attacker with valid credentials. If the attacker has access to the database, the engine returns the data decrypted: that is what the key is for. It does not protect against an IDOR, nor against ransomware operated with stolen credentials.
- The key is the real asset. Encrypting with a key that sits in the same place as the data is a decorative exercise. Key management — where the keys live, who has access, how they are rotated — is the hard problem, and it is studied in 03-06.
The cryptographic mechanisms are developed in module 3; what matters here is where to apply them.
4.3 Backups: the 3-2-1 rule and immutability
This is the most important measure in the whole lesson, because it is the only one that gives the business back when everything else has failed.
The 3-2-1 rule:
- 3 copies of the data (production and two more).
- On 2 different media or technologies.
- 1 of them out of reach of the main infrastructure.
The modern extension, 3-2-1-1-0, which is the one that matters against ransomware:
- 1 copy that is immutable or offline.
- 0 errors in the restore test.
Why immutability is the decisive piece. Remember the ransomware timeline from 02-02: on day 20, before encrypting, the attacker deletes the backups. If Nimbus's backups sit in the nimbus-backups-prod bucket (A-03), inside the same cloud account (A-05) and reachable with the same credentials as production, then they are not backups: they are more files that are going to be encrypted.
Target configuration for Nimbus:
| Requirement | Implementation |
|---|---|
| Separate account | The backups go to a different cloud account, with credentials production does not know |
| Immutability | Object lock in compliance mode, with 30-day retention: nobody, not even the administrator, can delete them before then |
| Encryption | With its own key, different from production's |
| Verification | A full, timed restore every quarter, with the result documented |
| Scope | Database, attachments bucket, infrastructure configuration and secrets (often forgotten) |
The sentence worth internalising: a backup that has never been restored is not a backup, it is a hope. The most common form of failure is not that the backup is missing: it is that the backup existed but was corrupt, incomplete, or nobody knew the procedure. Continuity and recovery time objectives are developed in 04-06.
4.4 DLP, minimisation and pseudonymisation
| Measure | What it does | Application at Nimbus |
|---|---|---|
| DLP (data loss prevention) | Detects and blocks sensitive data leaving by e-mail, web or USB | Realistic for Nimbus in a light version: alert if a file with many DNIs or e-mail addresses goes out; block USB write access |
| Minimisation | Not collecting or keeping what is not needed | Does Nimbus need patients' DNI (the Spanish national ID number)? Does it need to keep the appointment history for five years? What is not there cannot leak |
| Pseudonymisation | Replacing identifiers with references reversible only with separately held information | The pre-production environment (A-22) must use pseudonymised data, never a direct copy of production |
| Anonymisation | Irreversible transformation | For aggregate metrics and usage statistics |
| Retention and deletion | Removing data when it is no longer needed | A written policy per data type and automated deletion |
Minimisation is the measure with the best cost/benefit ratio in this section, and it is almost never applied because it requires saying no to data that "might be useful one day". Data that does not exist does not leak, does not have to be encrypted, does not have to be audited and does not appear in a breach notification.
Note on legal implications: minimisation, pseudonymisation, retention periods and deletion carry specific regulatory requirements, especially when the data can reveal health information, as is the case with clinic diaries. The decisions must be validated with the compliance owner or with legal advice; the handling is covered in 06-03.
- Protecting the application and the development life cycle
The API (A-04) is Nimbus's main surface. This layer shifts security to the left: to the moment the code is written, where fixing is cheap.
5.1 Code review and automated analysis
| Practice | What it finds | Cost |
|---|---|---|
| Peer review with a security perspective | Authorisation logic, design decisions, things no tool sees (the IDOR) | Nothing extra if changes are already reviewed |
| SAST (static analysis) | Dangerous patterns in the code: SQL concatenation, verify=False, pickle.loads |
Low; initial noise that has to be tuned |
| SCA (composition analysis) | Dependencies with known vulnerabilities | Low; the one with the highest immediate return |
| Secret scanning | Credentials in the code and in the history | Very low; essential after the token finding of 01-04 |
| DAST (dynamic analysis) | Flaws visible by attacking the running application | Medium; useful in pre-production |
The four-question checklist Iván should apply on every review, already introduced in 02-02:
- Who are you? — does the endpoint require authentication?
- Is it yours? — is it filtered by
tenant_idand is ownership of the resource verified? - Is what you are sending valid? — is there type and range validation?
- How much does this cost? — is there pagination, are there limits and rate limiting?
5.2 Secrets management
The finding of the non-expiring token in 01-04 and the leaked .env in the chained attack of 02-02 have the same root: secrets live where they should not.
| Rule | Why |
|---|---|
| Never in the code or in the repository | Git keeps the history: deleting the file does not delete the secret |
| In a secrets manager, with logged access | A single place to audit and rotate |
| Injected at run time, not into the container image | An image with secrets is a secret published in the registry |
| Short-lived wherever possible | Cloud provider credentials with a life of minutes instead of permanent keys |
| Rotatable without deploying | If rotating requires a deployment, it will not get rotated |
| On detecting an exposure: rotate first, investigate afterwards | The other way round is the classic mistake |
5.3 HTTP security headers
A set of instructions to the browser that takes a few minutes to configure and neutralises whole classes of attack from section 6 of 02-02.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self';
img-src 'self' data:; connect-src 'self' https://api.nimbusreservas.example;
frame-ancestors 'none'; base-uri 'none'; form-action 'self';
report-uri https://csp.nimbusreservas.example/informe
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()
Cache-Control: no-storeAn explanation of each one, and which attack it stops:
| Header | What it does | Attack it neutralises |
|---|---|---|
Strict-Transport-Security |
Forces the browser to use HTTPS for 2 years, including subdomains, and not to allow a manual exception | Downgrade to HTTP and interception (02-02, section 3) |
Content-Security-Policy |
Declares where each type of resource may be loaded and executed from | XSS: even if code is injected, the browser refuses to run it |
frame-ancestors 'none' |
Stops another website embedding the page in a frame | Clickjacking |
X-Content-Type-Options: nosniff |
Stops the browser guessing the content type | Uploaded files interpreted as executable code |
Referrer-Policy |
Limits the information sent when navigating to another site | Leakage of identifiers and internal paths in the Referer header |
Permissions-Policy |
Disables browser features the application does not use | Surface reduction on the client |
Cache-Control: no-store |
Stops responses containing personal data being cached | Recovery of data on a shared machine |
Details of the CSP that make the difference:
default-src 'none'as the base. You start from "nothing allowed" and open up only what is strictly necessary. It is the secure by default principle from 01-03 applied to the browser.- No
'unsafe-inline'and no'unsafe-eval'. Allowing them cancels most of the CSP's value against XSS. It requires the JavaScript to be in files, not inline. report-uriturns the CSP into a detective control: every violation is logged. A spike in CSP reports is usually the first sign of an XSS attempt. It is best deployed first in report-only mode (Content-Security-Policy-Report-Only) so as not to break the SPA.
5.4 Rate limiting
It has appeared in every section, and for one reason: it is the measure that takes the profit out of most automated attacks — brute force, enumeration, mass export, application-layer DDoS, business logic abuse.
| Dimension of the limit | What for |
|---|---|
| Per IP | Automated noise; not very effective against distributed attackers |
| Per user or token | Enumeration and mass export; the most useful one in an API |
| Per tenant | Stopping one customer consuming everyone else's capacity |
| Per sensitive operation | Log-in, password reset, export: far stricter limits |
The AppSec details are developed in 05-05.
5.5 A CI workflow with dependency and secret scanning
This is the file Iván would add to the Nimbus repository. It is explained step by step afterwards.
name: security
on:
push:
branches: [main]
pull_request:
schedule:
- cron: "0 6 * * 1" # Mondays at 06:00: periodic review
permissions:
contents: read # least privilege: the workflow only reads the code
jobs:
analysis:
runs-on: ubuntu-latest
steps:
# 1. Checking out the code. The action is pinned by hash, not by tag.
- name: Check out code
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
with:
fetch-depth: 0 # full history: needed to search for
# secrets in old commits
- name: Set up Python
uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38
with:
python-version: "3.12"
# 2. Dependencies with known vulnerabilities (SCA)
- name: Dependency scanning
run: |
pip install pip-audit
pip-audit --requirement requirements.txt \
--format json --output audit.json || true
pip-audit --requirement requirements.txt \
--vulnerability-service osv --strict
# --strict returns an error code if there are vulnerabilities:
# that way the build FAILS and nobody can ignore it
# 3. Secrets in the code and in the WHOLE history
- name: Secret scanning
uses: gitleaks/gitleaks-action@83373cf2f8c4db6e24b41c1a9b086bb9619e9cd3
env:
GITLEAKS_CONFIG: .gitleaks.toml
# 4. Dangerous patterns in our own code (SAST)
- name: Static analysis
run: |
pip install bandit
bandit -r app/ -ll -f screen
# -ll: medium and high severity only, so the signal is not drowned out
# 5. Component inventory (SBOM): answer "what do we use?" in minutes
- name: Generate SBOM
run: |
pip install cyclonedx-bom
cyclonedx-py requirements -i requirements.txt -o sbom.json
- name: Store results
if: always() # stored even if a step has failed
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: security-reports
path: |
audit.json
sbom.jsonThe seven decisions in this file, explained:
permissions: contents: read. By default, many workflows receive broad permissions. Declaring the minimum stops a compromised action writing to the repository. It is least privilege (01-03) applied to CI.- Actions pinned by commit hash, not by
@v4. A tag can be repointed at other code; a hash cannot. It is the defence against the supply chain attack of 02-02. fetch-depth: 0. Without the full history, the secret scan only looks at the last commit and misses exactly what has to be found: the credential somebody uploaded and "deleted" eight months ago.--strictin the dependency scan. Without it, the report gets generated and nobody reads it. With it, the build fails and a decision has to be made: update, or justify the exception in writing. An analysis that cannot block does not change behaviour.-llin the static analysis. Filtering by severity is what avoids the usual fate of these tools: 400 warnings, nobody looks at them, the tool gets switched off.- SBOM. When the next critical vulnerability in a famous library comes out, the question will be "does it affect us?". With an SBOM that is answered in minutes; without one, in days. It is the 01-04 inventory applied to software.
if: always()when storing results. The reports from the day something fails are precisely the ones you need to keep.
And a warning about what this workflow does NOT do: it does not find the IDOR, it does not find business logic abuse and it does not find an authorisation design flaw. Automated tools cover the known patterns; human judgement covers the rest. Complementary, not substitutes.
- Logging and observability as a protection measure
The CSF diagnosis in 02-01 was clear: Nimbus's weakest function is Detect. And without logging there is no possible detection.
6.1 What to log
| Category | Events | Why |
|---|---|---|
| Authentication | Successful and failed log-ins, MFA, log-outs, password changes | Detects password spraying, stuffing, anomalous sessions (02-02) |
| Authorisation | Access denials, use of administrative functions, customer impersonation | Detects escalation and IDOR |
| Access to sensitive data | Queries and exports with actor, tenant and volume | This is what detected the exfiltration in the 02-02 exercise |
| Configuration changes | Permissions, network rules, roles, bucket policies | Detects attacker persistence |
| Account life cycle | Joiners, leavers, role changes, token creation | Detects accounts created by the attacker |
| Application events | 5xx errors, SQL errors, CSP violations | Early signals of an exploitation attempt |
| Infrastructure | SSH access, container execution, image changes | Lateral movement |
The five fields every useful event must have: when (with time zone and a synchronised clock), who (a named identity, not "admin"), what (the specific action), on what (resource and tenant) and from where (IP and agent). A log with no identifiable actor is useless for investigation; a log with no resource is useless for measuring the scope.
6.2 What must NEVER be logged
This section is as important as the previous one, because a badly designed log creates a security problem instead of solving one.
| Never log | Why |
|---|---|
| Passwords, not even failed ones | A failure is usually a real password mistyped, or one from another service |
| Tokens, API keys, session cookies | A log with tokens is a file of valid credentials, and logs are copied and shared readily |
| Data revealing health information | The service name of an appointment ("knee rehabilitation") must not appear in an application log |
| Full card or account numbers | Specific obligations apply; at Nimbus payments are tokenised at the gateway precisely so as not to touch them |
| Full request and response bodies | It is the most common way of leaking all of the above without realising |
| Identity documents and unnecessary personal data | Minimisation applies to logs too |
# === BAD: logs the full body, including personal data and tokens ===
logger.info("Request received: %s headers=%s body=%s",
url, dict(request.headers), await request.body())
# === GOOD: log what is needed to investigate, and nothing more ===
logger.info(
"access",
extra={
"user_id": current_user.id, # identity, not full name
"tenant_id": current_user.tenant_id,
"action": "export_bookings",
"resource": "bookings",
"record_count": len(rows), # the VOLUME is the alerting signal
"ip": request.client.host,
"session_id": session.id,
# no patient names, no token, no request body
})Why this version makes detection possible and the previous one does not: the record_count field, together with session_id and tenant_id, is exactly what made the detection query in exercise 3 of 02-02 work. Detection is designed when the log is written, not when the incident happens. And the correct log contains fewer personal data than the incorrect one, not more.
6.3 The three properties that make a log useful
- Centralised. Logs spread across five machines cannot be correlated. What is more, an attacker who compromises a machine deletes its local logs: shipping them off the box in real time is what preserves the evidence.
- Intact and retained. The append-only audit table from 01-01 — with
INSERTbut withoutUPDATEorDELETEfornimbus_api— is this idea in practice. Minimum advisable retention: 90 days hot, one year cold. Many breaches are discovered months later, and without logs the scope cannot be determined, which is exactly what has to be communicated to customers and authorities. - With alerts somebody attends to. A log with no alerts is a historical archive. Few alerts and very good ones: for Nimbus, five initial rules are enough.
The five alerts to start with tomorrow:
| Alert | Initial threshold | Attack it detects |
|---|---|---|
| Authentication failures from one origin against several accounts | > 5 distinct accounts in 10 min | Password spraying |
| Anomalous data export | > 5,000 records or > 3 tenants per session/hour | Exfiltration |
| Use of an administrative function outside working hours | Any between 22:00 and 07:00 | Compromise of a privileged account |
| Change to network rules, IAM permissions or bucket policies | Any not originating in CI | Attacker persistence |
| Creation of a token, SSH key or new account | Any | Persistence |
Monitoring and detection techniques are developed in 05-02.
- Prioritising on an SME budget: the 12 highest-return measures
Nimbus cannot do everything. This is the table that turns the catalogue into decisions. Cost is money, effort is the team's time, impact is real risk reduction.
| # | Measure | Cost | Effort | Impact | What it neutralises |
|---|---|---|---|---|---|
| 1 | Phishing-resistant MFA on all administrative accounts (cloud, GitHub, mail, DB, VPN) | Low | Low | Very high | Spraying, stuffing, credential phishing, the consultancy's access |
| 2 | Immutable backups in a separate account + quarterly restore test | Low-medium | Medium | Very high | Ransomware, accidental or malicious deletion |
| 3 | Closing the surface found (SSH 0.0.0.0/0, Redis without auth, http.server, node_exporter, dead subdomains) |
None | Low | Very high | Automated opportunistic attack |
| 4 | Expiry and minimal scope on all tokens + rotation of the deployment token | None | Low | High | The chained attack of 02-02 |
| 5 | Dependency and secret scanning in CI (the yaml from section 5.5) |
None | Low | High | Vulnerable dependencies, leaked secrets |
| 6 | Centralised logging + the 5 alerts from section 6.3 | Low | Medium | Very high | Everything that is invisible today |
| 7 | SPF -all, DKIM and DMARC p=reject + external e-mail banner |
None | Low-medium | High | Domain spoofing, CEO fraud |
| 8 | Payment verification and support identity verification procedures | None | Low | High | BEC, IBAN fraud, vishing |
| 9 | Patch management with a committed deadline (7 days for critical ones) | Low | Medium | High | WannaCry, Equifax and their equivalents |
| 10 | Disk encryption on the 40 laptops with key custody | None | Low | Medium-high | Laptop theft or loss |
| 11 | Minimal segmentation: isolated guest wifi, closed data zone, bastion with MFA | Low | Medium | High | Lateral movement, ransomware |
| 12 | EDR on the laptops with somebody attending to the alerts | Medium | Medium | High | Malware, session theft, lateral movement |
Three observations about this table:
- Eight of the twelve measures have zero or low financial cost. What Nimbus lacks is not budget: it is decision and allocated time. It is the most repeated conclusion in SME security and the hardest one to accept.
- The only one with a real medium cost is the EDR (#12), and it comes last on purpose: without MFA, without immutable backups and without logging, an EDR is a badly placed luxury.
- None of the twelve is a perimeter security product. There is no new firewall on the list, and that is not an oversight: Nimbus's risk is not there.
- Phased plan: 30, 90 and 180 days
flowchart LR
F1["DAYS 1-30\nURGENT\nClose what is open,\nMFA, tokens,\nimmutable backups"]
F1 --> F2["DAYS 31-90\nSTRUCTURAL\nLogging and alerts,\nsecurity CI,\nmail and processes"]
F2 --> F3["DAYS 91-180\nMATURITY\nSegmentation, EDR,\nformal patching,\nrecertification"]
F3 --> F4["ONGOING\nQuarterly review,\nsimulations, restore\ntests"]
Phase 1 — Days 1 to 30: stop the bleeding
Everything that can be done with no budget, no supplier and no project.
| Task | Owner | Verification that it is done |
|---|---|---|
Close SSH to 0.0.0.0/0; access only through the bastion |
Lucía | describe-security-groups with no 0.0.0.0/0 on administrative ports |
Shut down the http.server, isolate the forgotten NAS, withdraw the public node_exporter and snmpd |
Lucía | ss -tulpn showing only the intended services |
| Authenticate Redis and bind it to the private network | Lucía | Connection from outside refused |
| Mandatory MFA on cloud, GitHub, mail and VPN, including the consultancy | Marta | Report of accounts without MFA: empty |
Rotate the nimbus-deploy-bot token; minimal scope and expiry |
Marta | No token without an expiry date |
| Revoke the public link to the payroll sheet and review accesses | Sara | Link inaccessible; log reviewed |
| Backups to a separate account with a 30-day object lock | Lucía | Deletion attempt refused by the provider itself |
| One full, timed restore | Lucía | Document with time taken and result |
| External e-mail banner | Marta | Visible in a test e-mail |
| Payment verification procedure, signed | Sara + Marta | One-page document published |
| Phishing reporting channel communicated to all 38 | Sara | Test report completed |
Phase 2 — Days 31 to 90: build capability
| Task | Owner | Expected outcome |
|---|---|---|
| Centralised logging with the fields from section 6.1 | Lucía | All authentication and data access events in a single place |
| The 5 initial alerts, with a rotating on-call | Lucía | Each alert with an owner and a one-line procedure |
| The security CI workflow from section 5.5 | Iván | A build that fails on a high-severity vulnerability |
| Fix whatever the first scan reveals | Iván | Zero outstanding critical vulnerabilities |
SPF to -all, DKIM and DMARC at p=none with reports |
Lucía | rua reports received and senders identified |
| HTTP security headers, CSP in report mode | Iván | Headers present; CSP reports collected |
| Rate limiting on authentication, export and expensive endpoints | Iván | Load test confirming the limit |
| TLS on the internal hop (flow F3) | Lucía | No internal traffic in the clear |
| Identity verification procedure in support | Rubén + Marta | Document plus a rehearsal with real cases |
| First phishing simulation with metrics | Sara | Report rate and time to first alert measured |
| Disk encryption on the 40 laptops with key custody | Lucía | Inventory at 100 % |
Phase 3 — Days 91 to 180: maturity
| Task | Owner | Expected outcome |
|---|---|---|
DMARC to p=quarantine and then p=reject |
Lucía | Domain protected against spoofing |
| CSP in blocking mode | Iván | No regressions in the SPA |
| Segmentation: guests isolated, data zone closed, bastion | Lucía | Network diagram updated and verified |
| EDR deployed, with a defined alert owner | Lucía | 100 % laptop coverage |
| Patch management with deadlines and measurement | Lucía | Dashboard with patch age per machine |
| Just-in-time access with expiry for the consultancy (A-19) | Marta | Permanent access removed |
| First access recertification | Marta | Access list reviewed and signed off |
| Pre-production with pseudonymised data | Iván | Classification of A-22 resolved (no longer "to be reviewed") |
| Second restore test, timed | Lucía | Recovery time known and documented |
| First external penetration test | Marta | Report with a prioritised remediation plan |
How this is sustained over time: a one-hour quarterly review looking for what is left over (01-04), one simulation per quarter, one restore test per quarter and an access recertification every six months. Four routines, all of them in the calendar. What is not scheduled does not happen.
Note: the deadlines in this plan are indicative and must be adjusted to each organisation's reality. The formal obligations of policy, documented control and auditable evidence are covered in 04-02, 04-03 and 06-04.
Common Mistakes and Tips
Common mistakes:
- Buying before closing. Acquiring a WAF or an EDR while SSH is open to the Internet is investing in the roof with the foundations unfinished.
- Relying on the WAF so as not to fix the code. The WAF buys time; it does not fix an injection and it does not see an IDOR.
- Calling something a backup when it has never been restored. The most common failure is not the absence of backups but their uselessness at the moment of truth.
- Keeping the backups in the same account and with the same credentials as production. Modern ransomware looks for them and deletes them: that is not a backup.
- Logging full request bodies. It turns the logging system into a store of tokens and personal data, and multiplies the impact of any leak.
- Generating a hundred alerts nobody looks at. Worse than having no alerts, because it produces a feeling of coverage.
- Deploying CSP straight into blocking mode. It breaks the SPA and ends up switched off forever. Report mode first.
- Leaving the CI analysis with no ability to block. A report that stops nothing changes nobody's behaviour.
- Encrypting and considering it solved. Encryption at rest does not protect against stolen credentials or against an authorisation flaw.
- Writing the plan with no owner and no date. A task with no name and no day is an intention.
Tips:
- Always start with point 3 of the table: close what is already open. Zero cost, maximum impact, and it reduces future work too.
- For every measure you put in place, write down how you would check that it is still working in six months' time. Defences degrade silently.
- Prefer what works by default over what demands daily discipline. In a 38-person company, discipline runs out; configuration stays.
- When torn between two measures, pick the one that reduces the damage of a failure over the one that tries to prevent one more failure. The first works even when you get things wrong.
- Turn every incident and every simulation into one new alert. It is the zero-cost purple team from 02-01.
- Review the phased plan with Marta every 30 days, ticking off what is done and renegotiating what is not. A plan that is not reviewed dies in phase 1.
Exercises
Exercise 1 — Matching defences to attacks
For each of these eight attacks from lessons 02-02 and 02-03, state: the main measure that neutralises it, a second layer independent of the first, the type of control each one is (preventive, detective, corrective or deterrent) and the phase of the chain they act on.
- Password spraying against the team's accounts.
- SQL injection in the customer search box.
- Ransomware that encrypts the infrastructure and deletes the backups.
- IBAN change fraud for a supplier, sent to Sara.
- Slow data exfiltration using a stolen token.
- Stored XSS in the "notes" field of a booking.
- SSRF through a clinic's logo URL.
- A compromised third-party CI action.
Exercise 2 — Fixing a dangerous log
This is the Nimbus API's current logging code:
@app.post("/api/v1/sesion")
async def log_in(request: Request):
data = await request.json()
logger.info("Login attempt: %s", data) # (1)
user = authenticate(data["email"], data["password"])
if user is None:
logger.warning("Failed login for %s with password %s",
data["email"], data["password"]) # (2)
raise HTTPException(401, "Incorrect credentials")
token = issue_token(user)
logger.info("Login OK user=%s token=%s", user.email, token) # (3)
return {"token": token}
@app.get("/api/v1/reservas")
def list_items(current_user = Depends(get_current_user)):
rows = list_bookings(current_user.tenant_id)
logger.info("Bookings returned: %s", [dict(r) for r in rows]) # (4)
return rowsRequired: (a) identify the problems on each marked line and explain the specific risk of each; (b) rewrite the code with correct logging; (c) state which three alerts you could build on the corrected log and which attack each would detect; (d) explain why the correct log contains fewer personal data than the incorrect one and still allows more to be detected.
Exercise 3 — Defending the budget to management
Marta gives you 6,000 € a year and one day a week of the team's time for security during the first half of the year. A salesperson has offered her a 5,500 € package that includes a next-generation firewall for the Valencia office and an antivirus for the 40 laptops.
Required:
- Argue, with evidence from this module's lessons, why that purchase is not the best allocation of Nimbus's budget.
- Propose your alternative allocation of the 6,000 € and of the weekly day, with concrete line items.
- Identify the three zero-cost measures that should be done in the first two weeks, and justify the order.
- Explain which risk you consciously accept by not spending on the perimeter, and how you would communicate it to Marta in writing.
- Define three indicators you would present at six months to demonstrate that the investment has paid off.
Solutions
Solution 1
| # | Attack | Main measure | Second layer | Types | Phase |
|---|---|---|---|---|---|
| 1 | Password spraying | Phishing-resistant MFA (preventive) | Alert on failures from one origin against several accounts (detective) | Prev. + Det. | Delivery / Exploitation |
| 2 | SQL injection | Parameterised queries (preventive) | nimbus_api role with no DELETE and no DDL + RLS (preventive, limits the damage); alert on SQL syntax errors (detective) |
Prev. + Prev./Det. | Exploitation |
| 3 | Ransomware | Immutable backups in a separate account (corrective) | Segmentation and MFA that make lateral movement harder (preventive); EDR (detective) | Corr. + Prev./Det. | Actions |
| 4 | IBAN fraud | Verification through an alternative channel on the number held on file (preventive) | Dual approval by amount (preventive) + a record of the verification (detective/evidential) | Prev. + Prev. | Delivery |
| 5 | Exfiltration with a stolen token | Token expiry and minimal scope (preventive) | Alert on anomalous export volume (detective) + rate limiting per token (preventive) | Prev. + Det. | Actions |
| 6 | Stored XSS | Output encoding (preventive) | Content-Security-Policy with report-uri (preventive + detective) and HttpOnly cookies |
Prev. + Prev./Det. | Exploitation |
| 7 | SSRF | URL validation blocking internal ranges and without redirects (preventive) | Egress proxy with an allowlist (preventive) + alert on outbound requests to internal ranges (detective) | Prev. + Prev./Det. | Exploitation |
| 8 | Compromised CI action | Pinning actions by commit hash (preventive) | CI without access to production secrets and with minimal permissions (preventive); alert on an outbound connection to a new domain from the runner (detective) |
Prev. + Prev./Det. | Delivery |
The pattern that emerges: in all eight cases, the second layer is of a different nature from the first — if the first is in code, the second is configuration or detection. That is defence in depth properly understood: layers that do not fail for the same reason. Two layers that both depend on Iván remembering something are, in practice, a single layer.
Solution 2
(a) Problems on each line:
| Line | Problem | Specific risk |
|---|---|---|
| (1) | Logs the full body of the log-in, including the password in the clear | Every password belonging to the team and to customers ends up in plain text in the logging system, which gets copied, exported and often sent to a third party |
| (2) | Explicitly logs the failed password | A failed password is usually the real password mistyped, or the one from another service. It is a file of reusable credentials |
| (3) | Logs the issued token | Anyone with access to the logs can use that token to take over the session, bypassing MFA (pass-the-cookie from the 02-03 exercise) |
| (4) | Logs every booking returned | Patient names, dates and services: data revealing health information, in an application log with no access control equivalent to the database's |
(b) Corrected code:
@app.post("/api/v1/sesion")
async def log_in(request: Request):
data = await request.json()
email = data.get("email", "")
user = authenticate(email, data.get("password", ""))
if user is None:
logger.warning("auth_fail", extra={
"email_hash": hashlib.sha256(email.lower().encode()).hexdigest()[:16],
"ip": request.client.host,
"agent": request.headers.get("user-agent", "")[:120],
})
raise HTTPException(401, "Incorrect credentials") # uniform message
token, jti = issue_token(user) # jti = token identifier, not the token
logger.info("auth_ok", extra={
"user_id": user.id,
"tenant_id": user.tenant_id,
"jti": jti,
"ip": request.client.host,
"mfa": user.mfa_verified,
})
return {"token": token}
@app.get("/api/v1/reservas")
def list_items(current_user = Depends(get_current_user), page: int = 1):
rows = list_bookings(current_user.tenant_id, page)
logger.info("data_access", extra={
"user_id": current_user.id,
"tenant_id": current_user.tenant_id,
"action": "list_bookings",
"record_count": len(rows), # the volume, not the content
"page": page,
})
return rowsThree decisions worth commenting on:
email_hashinstead of the address on failures. It allows attempts against the same account to be correlated without accumulating a list of valid e-mail addresses in the logs. It is minimisation applied to detection.jtiinstead of the token. Thejtiis the token's identifier: it serves to correlate the session and to revoke it, but it does not allow anyone to be impersonated. Detailed in 02-05.- A uniform error message. "Incorrect credentials" whether the address does not exist or the password is wrong: it avoids the user enumeration of 02-02.
(c) Three alerts you can build:
| Alert | Conceptual query | Attack detected |
|---|---|---|
| Spraying | auth_fail grouped by ip, counting distinct email_hash > 5 in 10 min |
Password spraying (02-02) |
| Exfiltration | data_access grouped by user_id, summing record_count > 5,000/hour |
Mass export with a stolen token |
| Session with no preceding authentication | data_access with a jti that has no corresponding auth_ok in the last 24 h |
Pass-the-cookie / session theft |
(d) Why fewer data allow more to be detected. The incorrect log accumulates content (passwords, tokens, patient names) that is no use for detecting anything: nobody builds an alert on a patient's name. The correct log accumulates structured metadata — who, what, how much, from where — which is exactly what gets queried and aggregated. The consequence is twofold and very elegant: you detect more and you reduce the impact of a leak of the logs themselves, which stop being a valuable target. Logging well is simultaneously a detection improvement and a data protection measure.
Solution 3
1. Why the proposed purchase is not the best allocation. Four arguments, backed by the module:
- It does not attack Nimbus's real risk. The main surface is the API in the cloud and identities, not the Valencia office network. An NGFW in the office protects neither the 19 remote employees, nor the cloud account (A-05), nor the API, nor the buckets.
- A signature-based antivirus does not cover what actually happens. Look back at the myth 2 table in 02-01: it does not stop credential phishing, nor the IDOR, nor the misconfigured bucket, nor the consultancy's credential without MFA, nor CEO fraud.
- It consumes 92 % of the budget, leaving measures 1 to 8 of the table in section 7 unfunded — the ones with very high impact and low or zero cost.
- Nimbus has known, unresolved problems today — open SSH, Redis without authentication, a permanent token, untested backups, zero detection — that neither piece of the package fixes. Spending on what is missing before closing what is open is the mistake from the common mistakes section.
2. Alternative allocation:
| Line item | Amount | Justification |
|---|---|---|
| Managed EDR for 40 machines | 2,400 € | The only measure in the table with a real cost; it covers the endpoint and provides detection and the ability to isolate |
| Simulation and micro-learning platform | 700 € | Attacks the dominant vector (02-03); measures the report rate |
| Immutable backup storage in a separate account | 600 € | It is measure #2 in the table; the cost is storage |
| Secrets and password manager for the team | 500 € | Solves the permanent token and password reuse |
| Managed centralised logging (basic tier) | 900 € | Enables the Detect function, the weakest according to the CSF |
| Contingency reserve | 900 € | A finding from the first scan usually requires unplanned spending |
| Total | 6,000 € |
The weekly day is allocated as follows: the whole of phase 1 in the first four weeks (all zero cost), then alternating implementation (logging, alerts, CI) with routine (reviewing alerts, patching, the quarterly review of what is left over).
3. The three zero-cost measures for the first two weeks, in order:
- Close the exposed surface (SSH
0.0.0.0/0, Redis without authentication,http.server,node_exporter,snmpd). It goes first because it is exploitable right now by a bot without anybody deciding to attack Nimbus: it is active risk, not potential risk. - MFA on all administrative accounts, including the consultancy's. It goes second because it neutralises spraying, stuffing and most credential phishing in one stroke, and that is the dominant vector.
- Rotate the permanent token and put an expiry on every token. It goes third because it closes link 2 of the chained attack in 02-02, and because a leaked token makes MFA irrelevant.
4. The risk accepted and how to communicate it. By not investing in the office perimeter, a residual risk on the Valencia local network is accepted: a compromised device in the office would have more freedom of movement than is desirable. It is partly mitigated at no cost with segmentation on the existing router (guests isolated, data zone closed) and with EDR on the laptops.
The communication to Marta must be written, explicit and dated:
"Accepted risk (date, review in 6 months): the Valencia office perimeter will not be reinforced this half-year. Reason: 100 % of customer data is in the cloud and half the headcount works outside the office, so the physical perimeter is not the main vector. Mitigation applied: segmentation with the current equipment, EDR on every laptop and disk encryption. To be reviewed if the architecture changes or if the office headcount grows. Approved by: Marta Solves."
This is not bureaucracy: it is the difference between accepting a risk (a conscious, documented, reviewable decision) and ignoring it (which is what gets discovered after an incident). The formalisation of this process is studied in 04-01.
5. Three indicators at six months:
| Indicator | Starting value | 6-month target | What it demonstrates |
|---|---|---|---|
| Administrative accounts without MFA | ~8 | 0 | The dominant vector closed |
| Verified recovery time (full timed restore) | Unknown | Known and < 4 h | Real ability to survive ransomware |
| Phishing report rate and time to first alert | 11 % / 47 min | > 35 % / < 15 min | That the human layer is working as detection |
A fourth, very visual indicator for management: the number of services exposed to the Internet, which should fall from six to one in the first month. It is the one that communicates progress best to somebody non-technical.
Conclusion
You have the catalogue of the response. You have learned first the method for choosing: which specific attack each measure neutralises, which phase of the chain it acts on, what type of control it is — preventive, detective, corrective or deterrent — and whether it will survive a bad day in a 38-person company. In perimeter and network you have seen the types of firewall and the precision that avoids the most expensive mistake: a WAF buys time and gives visibility, but it does not fix code and does not see an IDOR; you have understood segmentation as the most cost-effective measure against lateral movement, the dual nature of anti-DDoS defence — volumetric, which is bought in, and application-layer, which is programmed — and the evolution of remote access from the all-or-nothing VPN towards just-in-time access with an expiry. In endpoint you have distinguished antivirus from EDR by what really separates them — behaviour, telemetry and the ability to isolate — and you have put patching in its place: the least glamorous measure and one of those that would have prevented the most serious incidents.
In data protection you take away three ideas that hold everything else up: that encryption at rest does not protect against valid credentials and that the key is the real asset; that the 3-2-1 rule extended with immutability and a restore test is the only defence that gives the business back when everything has failed, because modern ransomware seeks out and deletes the backups; and that minimisation is the measure with the best cost/benefit ratio, because what does not exist does not leak. In application and development life cycle you have worked through code review with its four questions, secrets management with its rule of rotating first and investigating afterwards, HTTP headers with a CSP built from default-src 'none', rate limiting as the universal profit-remover, and a real CI workflow whose seven decisions — minimal permissions, actions pinned by hash, full history, an analysis that blocks, filtering by severity, SBOM and preserving the reports — turn a decorative report into an effective control.
In logging and observability you have learned what to log, with its five essential fields, and above all what must never be logged; and you have confirmed in the exercise the most counter-intuitive conclusion of the lesson: the correct log contains fewer personal data than the incorrect one and allows far more to be detected, because detection is built on structured metadata, not on content. And you have finished with the most useful thing for an SME: the twelve highest-return measures — eight of them with zero or low financial cost, none of them a perimeter product — and a 30, 90 and 180-day plan with an owner and a verification for every task, sustained by four quarterly routines in the calendar.
Of those twelve measures, number one was MFA, and several of the rest revolve around the same thing: who you are and what you can do. That is no coincidence. When the perimeter blurs — cloud, remote working, third parties, mobiles — identity becomes the new perimeter. In the next lesson, Identity, Authentication and Access Control (02-05), we will study it in depth: the life cycle of an identity and orphaned accounts, what the current guidance says about passwords, the forms of MFA ordered by phishing resistance, SSO and the identity protocols with the exact difference between OAuth 2.0 and OpenID Connect, sessions and JWTs with their correct validation as against the naive one, the DAC, MAC, RBAC, ABAC and ReBAC authorisation models with Nimbus's concrete design, and privileged accounts, just-in-time access and recertification.
Fundamentals of Information Security Course
Module 1: Introduction to Information Security
- Basic Concepts of Information Security
- Types of Threats and Vulnerabilities
- Principles of Information Security
- Assets, Attack Surface and Threat Actors
Module 2: Cybersecurity
- Definition and Scope of Cybersecurity
- Types of Cyber Attacks
- Social Engineering and Phishing
- Protection Measures in Cybersecurity
- Identity, Authentication and Access Control
- Cybersecurity Incident Case Studies
Module 3: Cryptography
- Introduction to Cryptography
- Symmetric Cryptography
- Asymmetric Cryptography
- Hash Functions, HMAC and Password Storage
- Cryptographic Protocols
- Key Management, Certificates and PKI
- Applications of Cryptography
Module 4: Risk Management and Protection Measures
- Risk Assessment
- Security Policies
- Security Controls
- Third-Party and Supply Chain Risk
- Incident Response Plan
- Disaster Recovery and Business Continuity
Module 5: Security Tools and Techniques
- Vulnerability Analysis Tools
- Monitoring and Detection Techniques
- Penetration Testing
- Network Security
- Application Security
- System Hardening and Endpoint Security
- Cloud and Container Security
Module 6: Best Practices and Regulations
- Best Practices in Information Security
- Security Regulations and Standards
- Personal Data Protection and GDPR in Practice
- Compliance and Auditing
- Training and Awareness
- Ethics, Legal Aspects and Responsible Disclosure
