In the previous lesson you laid out the map: the domains of the discipline, the frameworks that organise it and — above all — the life cycle of an attack. Now we are going to fill that skeleton with content. This lesson works through the technical attacks executed against systems like Nimbus Reservas', ordered by the phase of the chain in which they appear: how the adversary gathers information, how it attacks the network, how it obtains credentials, how it exploits a web application, how it deploys ransomware or compromises a supply chain, and how it abuses an API. For each one you will see what it is, a minimal, conceptual example on the Nimbus architecture, how it is detected and where its defence points. The approach is defensive throughout: the examples are illustrative, minimal and always accompanied by their fix. Knowing the attack is not an end in itself, it is the prerequisite for designing the defence.

Contents

  1. How this catalogue is organised, and a warning first
  2. Phase 1 — Reconnaissance: OSINT and scanning
  3. Network attacks: eavesdropping, spoofing and interception
  4. Denial of service: volumetric and application-layer
  5. Credential and session attacks
  6. Web application attacks: the OWASP Top 10 as a framework
  7. Malware in action: modern ransomware and double extortion
  8. Supply chain attacks
  9. API-specific attacks
  10. A chained attack on Nimbus, step by step
  11. Summary table: attack, property affected, detection and defence

  1. How this catalogue is organised, and a warning first

A catalogue of attacks without structure is a list nobody can remember. That is why we use the axis you already know — the Kill Chain phases — and add a second reading key: which property of the CIA triad each attack breaks, the one you established in 01-01.

flowchart LR
    R["RECONNAISSANCE\nOSINT, scanning,\nenumeration"] --> E["DELIVERY AND EXPLOITATION\nNetwork, credentials,\nweb application"]
    E --> P["POST-EXPLOITATION\nPersistence, lateral\nmovement, escalation"]
    P --> A["ACTIONS\nExfiltration, encryption,\nfraud, denial of service"]

A necessary warning. Everything that follows is written in order to defend. You will not find ready-to-use attack tooling here, nor working malware code, nor instructions applicable to third-party systems. The examples are minimal fragments on the fictitious Nimbus environment, always with their fix alongside. Running any of these techniques against systems that are not yours, without express written authorisation, is a crime; the legal and ethical framework for authorised testing is covered in 06-06, and the methodology of penetration testing in 05-03.


  1. Phase 1 — Reconnaissance: OSINT and scanning

Reconnaissance is the phase organisations ignore because it leaves no trace in their systems: a good part of it happens without touching them.

2.1 OSINT: open source intelligence

What it is. Gathering publicly available information about the target. Nothing is exploited; what the organisation has published without realising it is simply read.

What an attacker finds about Nimbus without touching a single server:

Public source What it reveals Use to the attacker
Certificate transparency logs Every subdomain a certificate has been issued for, including pre., admin. and demo. Discovers internal environments nobody thought were public
The team's professional profiles Names, job titles, technologies they mention ("migrating to FastAPI and PostgreSQL") Phishing targets and technology stack
Public code repositories Configuration files uploaded by mistake, e-mail addresses in the commits, internal host names Credentials and internal surface
Job adverts "We are looking for a DevOps engineer with experience in X, Y, Z" A free and very reliable technology inventory
DNS and WHOIS records Mail, cloud and CDN providers Where mail comes in and where things are hosted
Credential leaks from other services Corporate e-mail addresses appearing in third-party breaches Basis for credential stuffing (section 5)
Published documents (PDFs, spreadsheets) Metadata with user names, internal paths, software versions Account naming conventions

How to defend against it. You cannot stop somebody reading what is public, but you can reduce what gets published unintentionally:

  • Periodically review the subdomains that appear in certificate transparency and withdraw those that no longer exist (this is the finding from exercise 3 of the previous lesson).
  • Scan the history of the repositories for secrets — deleting the file is not enough: in Git it is still in the history — and rotate any exposed credential.
  • Strip metadata from published documents.
  • Assume the team's list of e-mail addresses is public. The defence is not to hide it, it is MFA and training (02-03, 02-05).

2.2 Scanning and enumeration

What it is. Actively contacting the target's systems to discover what is there: which IPs answer, which ports are open, which services and versions run, which routes exist in the web application.

Unlike OSINT, scanning does leave a trace. And here is the asymmetry that matters for an SME: Nimbus is already being scanned continuously by bots that have no idea what it is. Telling targeted scanning apart from the background noise is hard; what is feasible is to look out for slow, methodical scans from a single source, which suggest real interest.

# Extract from the Nimbus load balancer log (illustrative fragment)
203.0.113.44 - - [12/Mar/2026:03:11:02] "GET /.env HTTP/1.1" 404 153
203.0.113.44 - - [12/Mar/2026:03:11:03] "GET /.git/config HTTP/1.1" 404 153
203.0.113.44 - - [12/Mar/2026:03:11:04] "GET /admin HTTP/1.1" 404 153
203.0.113.44 - - [12/Mar/2026:03:11:05] "GET /backup.sql HTTP/1.1" 404 153
203.0.113.44 - - [12/Mar/2026:03:11:06] "GET /wp-login.php HTTP/1.1" 404 153
203.0.113.44 - - [12/Mar/2026:03:11:07] "GET /phpmyadmin/ HTTP/1.1" 404 153

How to read this extract:

  • Six requests in six seconds from the same IP, all to routes that do not exist at Nimbus. It is an automated scanner working through the usual catalogue.
  • /wp-login.php and /phpmyadmin/ give away that the bot has no idea what technology Nimbus uses: it tries everything. This is the opportunistic attack from 01-04 in its purest form.
  • The most valuable defensive signal is not in the 404s, but in the day one of them returns a 200. That is why the useful alert is not "we are being scanned" (you always are), but "a sensitive route has returned something other than a 404".

Discovery and scanning tools are covered in 05-01 and 05-03. What matters here is the concept and its defensive counterpart: every service you do not expose is a service that appears in no scan at all.


  1. Network attacks: eavesdropping, spoofing and interception

These attacks go after the channel, not the endpoints. They share one premise: if the attacker manages to get in the middle or to listen in, the security of the endpoints stops being enough.

Attack What it does Requirement Property broken
Sniffing (eavesdropping) Captures traffic travelling across the network Being on the same segment or at a transit point Confidentiality
ARP spoofing Poisons the local network's ARP table so that traffic passes through the attacker Being on the same local network (the office wifi) Confidentiality, integrity
Man-in-the-Middle (MitM) Interposes itself and can read and modify the traffic Any of the above, or control of a network point Confidentiality, integrity, authenticity
DNS spoofing / poisoning Answers DNS queries with a false IP Control of the resolver, the network or the DNS account Authenticity, integrity
Rogue wifi access point Offers a network with a credible name ("NimbusGuest") that the victim connects to Physical proximity All

3.1 Why this still matters if everything goes over HTTPS

That is the right question. Properly implemented TLS makes passive eavesdropping useless: the attacker sees encrypted traffic. But three real gaps remain at Nimbus:

  1. Internal traffic that is not encrypted. In the 01-04 DFD, flow F3 (load balancer → API) was internal HTTP. Anyone who reaches that network reads it in the clear. This is the reason for the Zero Trust principle: the internal network is not trusted.
  2. Metadata is still visible. Even with encrypted content, an observer sees which domains you connect to, when and at what volume. The DNS lookup beforehand usually travels in the clear.
  3. Downgrade and user error. If a Nimbus laptop accepts an invalid certificate with one click, TLS stops protecting. That is why HSTS exists (we will see it in 02-04) and why clients must refuse, not ask.

Minimal example of the classic mistake in the Nimbus code:

# === VULNERABLE: disabling certificate verification ===
import requests

# Someone set verify=False "because in testing it gave a certificate error"
response = requests.get("https://api.pasarela-pago.example/v1/cobros",
                        headers=headers, verify=False)
# === CORRECT ===
import requests

# Certificate verification is what turns TLS into authentication of the
# server, not just encryption. Without it, anyone who interposes themselves
# can present their own certificate and read and modify the communication.
response = requests.get("https://api.pasarela-pago.example/v1/cobros",
                        headers=headers, timeout=10)   # verify=True is the default

Why verify=False is so serious and so common: it turns off the check that the certificate presented really does correspond to the domain and is signed by a recognised authority. The traffic is still encrypted, which gives a false sense of security, but encrypted against the attacker if the attacker has interposed themselves. It almost always appears as a temporary patch in a test environment and ends up in production. Practical rule: verify=False must not exist in any repository; if an internal certificate causes trouble, the answer is to add the internal authority to the trust store, not to disable verification. Static analysis in CI (02-04) picks it up automatically.

3.2 Detection

Signal Where it shows up
Unexpected changes in the ARP table; one MAC associated with several IPs Switch logs; ARP detection tools on the local network
DNS answers with anomalous TTLs or IPs outside the expected ranges Resolver logs
A certificate presented that differs from the expected one Certificate pinning in the mobile app; certificate transparency alerts
An SSID with the corporate name appearing away from the office Access point inventory; rogue access point alerts

  1. Denial of service: volumetric and application-layer

The objective is availability: stopping Nimbus's customers from using the service. For a SaaS that clinics' live diaries depend on, an hour of downtime has an immediate and visible cost.

Type How it works Volume required Example at Nimbus
Volumetric DoS Saturate the bandwidth or the connections from a single origin High Hard from a single origin; uncommon today
Volumetric DDoS The same from thousands of distributed origins, often amplifying the traffic through misconfigured third-party services Very high Saturation of the load balancer's link
Application-layer DDoS (layer 7) Few requests, but expensive ones: each consumes a lot of CPU, memory or database Low Requests to the unpaginated report from 01-01
Logical resource exhaustion Consuming a finite, non-technical resource Extremely low Booking and cancelling in a loop to fill a clinic's diary

The most dangerous for Nimbus is the third, and it is the most underestimated. Remember the unpaginated reports endpoint that broke availability in 01-01: 20 well-chosen requests can bring the API down, whereas a volumetric DDoS requires a botnet. Defence against the volumetric kind is bought (the provider's anti-DDoS service); defence against the application-layer kind is programmed.

# === VULNERABLE PATTERN: unlimited cost per request ===
@app.get("/api/v1/informes/ocupacion")
def occupancy_report(date_from: str, date_to: str, current_user = Depends(get_current_user)):
    # The client decides the range: it can ask for 10 years of data
    rows = db.execute(
        "SELECT * FROM bookings WHERE tenant_id = :t AND date_time BETWEEN :d AND :h",
        {"t": current_user.tenant_id, "d": date_from, "h": date_to}).fetchall()
    return [dict(r) for r in rows]        # and it all gets materialised in memory
# === FIX: bound the cost BEFORE executing ===
from datetime import date, timedelta
from fastapi import HTTPException

MAX_DAYS = 92          # business limit: one quarter
MAX_ROWS = 5000        # technical limit

@app.get("/api/v1/informes/ocupacion")
@limiter.limit("5/minute")                         # rate limit per user
def occupancy_report(date_from: date, date_to: date, page: int = 1,
                     current_user = Depends(get_current_user)):
    if (date_to - date_from) > timedelta(days=MAX_DAYS):
        raise HTTPException(400, "The maximum range is 92 days")

    rows = db.execute(
        """SELECT id, date_time, service_id, status
             FROM bookings
            WHERE tenant_id = :t AND date_time BETWEEN :d AND :h
            ORDER BY date_time
            LIMIT :lim OFFSET :off""",
        {"t": current_user.tenant_id, "d": date_from, "h": date_to,
         "lim": MAX_ROWS, "off": (page - 1) * MAX_ROWS}).fetchall()
    return {"page": page, "data": [dict(r) for r in rows]}

The four defences that appear here, explained:

  1. Type validation (date_from: date instead of str): rejects malformed input before it reaches the database.
  2. Business limit (92 days): a ten-year occupancy report is not a legitimate use case. Business limits are more effective than technical ones because they can be defended to the customer.
  3. Pagination with LIMIT: bounds the memory and the time of each request, whatever happens.
  4. Rate limit (5/minute): bounds how many times that cost can be paid. Without it, the other three limits only force the attacker to repeat more often.

Detection: average API latency shooting up with little inbound traffic, slow PostgreSQL queries concentrated on one endpoint, and a single user_id or token accounting for most of the consumption. In a volumetric DDoS these signals would be different: lots of traffic and network saturation.


  1. Credential and session attacks

Identity is today the dominant way in, because a valid credential sets off no alarm: no exploit, no malware, no protocol anomaly. Just somebody logging in.

Attack Mechanics What makes it viable Characteristic signal in the logs
Brute force Many passwords against one account No lockout or limit Many failures, one account, short time
Dictionary attack The same, but with lists of probable passwords Human, predictable passwords Same as above
Password spraying One very common password against many accounts Per-account lockout (which does not catch this) Few failures per account, many accounts, same IP
Credential stuffing E-mail/password pairs from other services' breaches Password reuse Low but non-zero success rate; distributed IPs
Session / token theft Using a stolen but valid token or cookie Long-lived tokens, not bound to context Same token from a new IP or country
Pass-the-cookie Extracting the session cookie from the victim's browser and importing it into another Cookies stolen by malware bypass MFA A session with no preceding authentication event

5.1 Password spraying: the one that evades the classic defence

It deserves special attention because it is designed to get around account lockout. If Nimbus locks out after 5 failed attempts per account, the attacker tries a single very common password against the team's 38 accounts, waits an hour and tries the next one. No account ever reaches 5 failures.

# Fragment of the Nimbus authentication log (illustrative)
2026-03-14T02:14:07Z auth FAIL user=marta@nimbusreservas.example  ip=198.51.100.77
2026-03-14T02:14:11Z auth FAIL user=ivan@nimbusreservas.example   ip=198.51.100.77
2026-03-14T02:14:15Z auth FAIL user=lucia@nimbusreservas.example  ip=198.51.100.77
2026-03-14T02:14:19Z auth FAIL user=ruben@nimbusreservas.example  ip=198.51.100.77
2026-03-14T02:14:23Z auth OK   user=sara@nimbusreservas.example   ip=198.51.100.77

How to read it: four failures and one success, one attempt per account, the same IP, four seconds between each, at two in the morning. No account has reached the lockout threshold, and yet the attack has succeeded. Correct detection does not count failures per account but distinct failures per origin and the ratio between accounts attacked and accounts that exist. This is exactly the kind of rule built in 05-02.

The defence that neutralises it almost completely is phishing-resistant MFA, together with checking passwords against leaked lists. Both are developed in 02-05.

5.2 Token theft: why the 01-04 finding was serious

In the inventory you discovered a token for the nimbus-deploy-bot account with no expiry and with write permissions on every repository. A token like that has three properties that make it the perfect target:

  • It does not expire: stealing it once is enough forever.
  • It has no second factor: a token is the complete credential.
  • It is in many places: in the CI configuration, on the laptop of whoever created it, perhaps in a .env file, perhaps in a chat history.

Its fix — short expiry, minimal scope, short-lived credentials from the CI itself and rotation — is detailed in 02-05.


  1. Web application attacks: the OWASP Top 10 as a framework

The OWASP Top 10 is the list of the ten most widespread risk categories in web applications. It is not an exhaustive catalogue of vulnerabilities: it is a list of categories by frequency and impact, and that is why it works well as a review guide. We work through the ones most relevant to the Nimbus API.

6.1 Broken access control (IDOR and company)

It is the first category in the Top 10 by prevalence, and you already came across it in 01-01.

What it is. The system authenticates correctly ("I know who you are") but does not authorise correctly ("I am not checking that this is yours").

# === VULNERABLE (the 01-01 endpoint) ===
@app.get("/api/v1/reservas/{booking_id}")
def view_booking(booking_id: int, current_user = Depends(get_current_user)):
    # There is a valid session, but whose booking this is never gets checked
    return db.execute("SELECT * FROM bookings WHERE id = :id",
                      {"id": booking_id}).fetchone()
# === FIXED ===
@app.get("/api/v1/reservas/{booking_id}")
def view_booking(booking_id: int, current_user = Depends(get_current_user)):
    row = db.execute(
        """SELECT id, date_time, service_id, status, end_customer_name
             FROM bookings
            WHERE id = :id AND tenant_id = :tenant""",   # the resource must be theirs
        {"id": booking_id, "tenant": current_user.tenant_id}).fetchone()
    if row is None:
        raise HTTPException(404)      # 404 and not 403: it does not reveal whether it exists
    return dict(row)

Variants of the same family worth recognising:

  • Horizontal escalation: reaching the data of another user at the same level (the classic IDOR).
  • Vertical escalation: a reception user obtains administrator functions because the control lives only in the interface and not on the server.
  • Parameter tampering: sending {"role": "admin"} in the body of a profile update and having the server accept it because it does a mass assignment of fields.

Detection. It is hard from outside and easy from the audit trail: if the log records user, tenant and resource, a query looking for accesses where the resource's tenant does not match the user's finds the attempts. Without that log, it is invisible. This is why the auditing of 01-01 and 01-03 was not bureaucracy.

6.2 SQL injection

What it is. The user's data is concatenated into a query and the engine interprets it as an instruction rather than as data.

# === VULNERABLE: string concatenation ===
def search_customers(text, tenant_id):
    query = f"SELECT id, name, email FROM end_customers " \
            f"WHERE tenant_id = {tenant_id} AND name LIKE '%{text}%'"
    return db.execute(query).fetchall()

If text contains a quotation mark, the structure of the query changes. This conceptual example is enough: a value such as ' OR '1'='1 turns the search condition into an always-true condition, and the query returns all the rows the database role has access to. More advanced variants make it possible to read other tables or to infer data character by character from the response time (blind injection).

# === CORRECT: parameterised queries ===
def search_customers(text, tenant_id):
    return db.execute(
        """SELECT id, name, email
             FROM end_customers
            WHERE tenant_id = :t AND name ILIKE :pattern
            LIMIT 100""",
        {"t": tenant_id, "pattern": f"%{text}%"}).fetchall()

Why parameterisation really works and "manual escaping" does not: with parameters, the engine receives the structure of the query first and then the values, already as data. There is no content of text that can change the structure, because the structure is already fixed. Manual escaping, by contrast, depends on getting every combination of quotation marks, encodings and engine modes right: sooner or later it fails.

Second layer (the one that saves you when the first fails): the nimbus_api role from 01-03 has no DELETE, no DDL and cannot read payroll or gateway_keys. An injection with that role is serious, but bounded. And PostgreSQL's RLS additionally prevents it from returning another tenant's rows. Defence in depth in its purest form.

Detection: SQL errors in the application logs (a syntax error at or near is an unmistakable signal that somebody is probing), spikes of anomalously slow queries, and requests with quotation marks or SQL keywords in parameters that should never contain them. A WAF catches many attempts, but it does not replace parameterisation.

6.3 Cross-Site Scripting (XSS)

What it is. The attacker gets another victim's browser to execute code in the context of the Nimbus site. The victim is not the server: it is the user.

Type Where the payload lives Example at Nimbus
Stored Saved in the database and served to everyone The "appointment notes" field displayed in the clinic's panel
Reflected Travels in the URL and comes back in the response A search box showing "No results for: what you typed"
DOM-based Never reaches the server; it happens in the SPA's JavaScript The SPA reads a parameter from the URL fragment and inserts it into the page

Minimal, conceptual example. The "notes" field of a booking accepts free text. If the SPA inserts it into the page without escaping, a text containing a <script> tag will execute in the browser of the clinic receptionist who opens that appointment. The impact is not "a pop-up window": it is that this code acts with the receptionist's session, and it can read the whole diary or perform actions in her name.

// === VULNERABLE: inserting HTML without escaping ===
element.innerHTML = booking.notes;

// === CORRECT: insert as text, never as HTML ===
element.textContent = booking.notes;

The three layers of defence against XSS, in order of importance:

  1. Context-aware output encoding. The same data is escaped differently in HTML, in an attribute, in JavaScript or in a URL. Modern template engines and SPA frameworks do it by default; the problem appears when somebody deliberately turns it off (innerHTML, dangerouslySetInnerHTML, |safe).
  2. Content-Security-Policy: a header telling the browser which origins it may load and execute code from. It turns many exploitable XSS flaws into failed attempts. Detailed in 02-04.
  3. HttpOnly cookies: they stop JavaScript reading the session cookie, limiting direct theft. They do not stop the code acting on behalf of the user. Detailed in 02-05.

6.4 CSRF (cross-site request forgery)

What it is. A malicious site makes the victim's browser, already authenticated at Nimbus, send an unwanted request. The browser attaches the session cookie automatically, so the request looks legitimate.

Example at Nimbus: a clinic administrator is authenticated in the panel and visits another page which, without her noticing, submits a form to POST /api/v1/usuarios/invitar to create a new user with permissions.

Defences, and why they have to be combined:

Defence How it works Limitation
SameSite=Lax or Strict cookie The browser does not send the cookie on requests originating from another site It is the main defence today; it requires modern browsers and Lax does not cover every case
Anti-CSRF token Each form includes an unpredictable value the server verifies Requires state management
Origin/Referer verification The server rejects requests from a different origin The headers are sometimes absent
Header-based authentication instead of cookies If the token travels in Authorization, the browser does not attach it by itself It changes the SPA's session model (see 02-05)

6.5 SSRF (server-side request forgery)

What it is. The attacker gets the Nimbus server to make a request to a URL of their choosing. It is especially dangerous in the cloud, because the server is inside the private network and can reach places nobody reaches from outside.

Example at Nimbus: a feature lets a clinic supply the URL of its logo for invoices, and the server downloads it. If it is not validated, the attacker can supply an internal address — the cloud provider's metadata service, an internal administration panel, localhost — and have the server fetch it on their behalf. The most serious case is access to the instance metadata service, which in older configurations can return temporary credentials for the cloud account (A-05).

# === VULNERABLE ===
def download_logo(url: str):
    return requests.get(url, timeout=5).content     # any URL, including an internal one

# === FIXED: allowlist, prior resolution and blocking of internal ranges ===
import ipaddress, socket
from urllib.parse import urlparse

BLOCKED_RANGES = [ipaddress.ip_network(r) for r in
                  ("127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12",
                   "192.168.0.0/16", "169.254.0.0/16", "::1/128")]

def download_logo(url: str):
    p = urlparse(url)
    if p.scheme != "https":                       # 1. HTTPS only
        raise ValueError("Scheme not allowed")
    ip = ipaddress.ip_address(socket.gethostbyname(p.hostname))
    if any(ip in net for net in BLOCKED_RANGES):      # 2. nothing internal
        raise ValueError("Destination not allowed")
    return requests.get(url, timeout=5, allow_redirects=False,   # 3. no redirects
                        stream=True).raw.read(2_000_000)          # 4. maximum size

The four decisions in the fixed code: HTTPS only (rules out file://, gopher:// and other schemes); resolving the name beforehand and checking that the IP does not fall in an internal range; not following redirects, because a redirect is the classic way of getting around the check; and a size limit so that the download does not turn into a denial of service. Even so, the definitive defence is architectural: have these requests go out through an egress proxy with an allowlist, and require the version of the metadata service that demands a token (covered in 05-07).

6.6 Insecure deserialisation

What it is. The application reconstructs objects from data that comes from outside. Some formats allow the reconstruction process to execute code.

# === VERY DANGEROUS: pickle over external data ===
import pickle
data = pickle.loads(request_body)   # can execute arbitrary code

# === CORRECT: a data format with no execution capability, plus validation ===
from pydantic import BaseModel

class CustomerPreferences(BaseModel):
    language: str
    timezone: str
    reminders: bool

data = CustomerPreferences.model_validate_json(request_body)

The rule: pickle, yaml.load without SafeLoader and their equivalents in other languages must never be applied to data coming from outside. Use JSON and validate the schema. Validating with an explicit model additionally provides a defence against mass assignment of fields: only the three declared keys are accepted.

6.7 Sensitive data exposure

Less spectacular and very frequent. Common forms in an API like the Nimbus one:

  • Returning more than necessary: a SELECT * that includes dni, internal_notes or hash_password, trusting the interface not to display them. The data travels to the client and is sitting in the browser.
  • Verbose error messages: a full stack trace with the library version, the file path and even the SQL query.
  • Logging what should not be logged: tokens, passwords or health data in the logs. Covered in 02-04.
  • Objects accessible without authorisation: the attachments bucket (A-02) with no restrictive policy. You already solved this with the 120 s signed URLs in 01-03.

6.8 Vulnerable dependencies and components

The Nimbus API drags along around 180 transitive Python dependencies. Iván has written a tiny fraction of the code that runs in production.

$ pip-audit
Found 3 known vulnerabilities in 2 packages
Name        Version  ID                 Fix Versions
----------- -------- ------------------ ------------
py-lib-x    2.4.1    GHSA-xxxx-xxxx-01  2.4.3
py-lib-x    2.4.1    GHSA-xxxx-xxxx-02  2.5.0
py-lib-y    1.9.0    PYSEC-2026-0001    1.9.4

How to read it and what to do: each line is a known vulnerability with the version that fixes it. What matters is not the list, it is the process: that this command runs on every build, that it breaks the pipeline when something of high severity appears, and that there is an explicit commitment to a remediation deadline. An analysis nobody reads is worse than none at all, because it creates the feeling of being covered. The full CI yaml is in 02-04.


  1. Malware in action: modern ransomware and double extortion

In 01-02 you saw the taxonomy of nine malware families. What matters here is how the one that does most damage to companies the size of Nimbus operates today.

What changed with respect to classic ransomware: it is no longer a file that encrypts a computer. It is a human-operated campaign, with several specialist groups involved, that lasts weeks and in which the encryption is the last step.

7.1 Typical timeline

Moment What happens Detectable?
Day 0 Initial access: phishing, a VPN credential without MFA, or an exposed unpatched service. Often obtained by a broker who sells the access Yes: anomalous authentication, unusual execution
Days 1-3 Internal reconnaissance: map of the network, where the backups are, who is an administrator Yes: directory queries, internal scanning
Days 3-10 Escalation and lateral movement; administrative credentials are obtained Yes: use of administrative tooling outside the usual pattern
Days 10-20 Exfiltration: the data is taken before encryption. This is the basis of the second extortion Yes: anomalous outbound volume towards new destinations
Day 20 Sabotage of recovery: deletion of backups, snapshots and replicas. This step decides the outcome Yes, and it is the last chance
Day 21 Encryption, usually on a Friday night or a public holiday, and the ransom note Too late
Afterwards Double extortion: pay to decrypt and pay for the data not to be published. Sometimes triple: the victim's own customers are notified

The three defensive conclusions that follow from this timeline:

  1. There is a three-week window. Ransomware is not instantaneous: it is the visible conclusion of a long compromise. Every day of that window is a detection opportunity that Nimbus is currently wasting because nobody looks at the logs.
  2. The backups are the objective, not collateral damage. The attacker actively looks for the backups and deletes them. That is why a backup accessible with the same credentials as production is not a backup: it is one more file that is going to be encrypted. The answer is immutability and separation of accounts (02-04, 04-06).
  3. Paying does not solve the leak. Even if the ransom is paid and the data decrypted, the data has already left. For Nimbus, holding clinic appointment data, that means a breach notification regardless of whether service is restored. The concrete implications are covered in 06-03, and the payment decision is analysed in the Colonial Pipeline case in 02-06.

Legal note: paying ransoms has legal, tax and sanctions implications that vary with the jurisdiction and with the identity of the attacking group. Any decision in a real case must be taken with legal advice and with the competent authorities.


  1. Supply chain attacks

What it is. Instead of attacking Nimbus directly, the adversary compromises something Nimbus trusts and waits for Nimbus to install or run it. It is an attack of extreme efficiency: compromise one, reach thousands.

Vector How it reaches Nimbus Conceptual example
Compromised code package A legitimate dependency gets a release containing malicious code A Python library used by the API publishes a trojanised version
Dependency confusion A public package with the same name as an internal one has a higher version and the package manager prefers it The internal nimbus-utils versus a malicious public nimbus-utils
Third-party CI/CD action A step in the GitHub Actions workflow is referenced by a movable tag and that tag is repointed A deployment action that steals the environment's secrets
Supplier with access The supplier is compromised and its legitimate access is used against the customer The systems consultancy (A-19)
Signed software update The vendor itself distributes a compromised update The SolarWinds case, which we will analyse in 02-06

Why it is so hard to detect: all of the above looks legitimate. A dependency installed by pip from the official index, a CI action with a familiar name, a connection from the consultancy during working hours. There is no protocol anomaly to detect; only subsequent behaviour.

Defences applicable at Nimbus today:

  • Pin exact versions and a lock file with checksums, so that the build is reproducible.
  • Pin CI actions by commit hash, not by tag:
# Vulnerable: the tag can be repointed at other code
- uses: some-org/deploy-action@v3

# Robust: the hash identifies specific, immutable code
- uses: some-org/deploy-action@a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
  • Least privilege in CI: the build workflow should not have access to production secrets, and the secrets should be short-lived.
  • Generate a component inventory (SBOM) so that "are we using the library that has just been in the news?" can be answered in minutes. It is the 01-04 inventory applied to software.

Treating the supplier as a manageable risk — assessment, contract, review — belongs to 04-04.


  1. API-specific attacks

The Nimbus API (A-04) is its main surface: ~120 endpoints used by the SPA and the mobile app. API attacks are not always "vulnerabilities" in the classic sense; very often they are legitimate uses taken to the extreme.

Attack What it exploits Example at Nimbus Defence
Identifier enumeration Sequential IDs and responses that distinguish "does not exist" from "not yours" Walking through /api/v1/reservas/{id} incrementing the number Unguessable identifiers (UUID); always answer 404; rate limiting
User enumeration Differences in the message or in the time of the response when logging in or resetting a password "That e-mail address is not registered" versus "Incorrect password" Identical response and timing in both cases
Business logic abuse The rules of the business, not code flaws Booking 200 slots and cancelling them, blocking a clinic's diary; applying a discount repeatedly Limits per user and per tenant; rule validation on the server; pattern detection
Missing rate limiting The absence of any cost to the attacker Trying passwords, exporting data, burning through the SMS balance Limits per IP, per user and per token; quotas per tenant
Excessive data exposure The server returns everything and the client filters The booking object includes dni and internal_notes Explicit response schemas, never SELECT *
Shadow endpoints Old or test versions that are still alive /api/v0/ without the new validations Endpoint inventory; explicit withdrawal of versions
Insecure consumption of third-party APIs The third party's response is trusted blindly Accepting a gateway webhook without verifying the signature Signature verification; validate what comes from a partner too

The most underestimated point: business logic abuse. There is no technical vulnerability in booking and cancelling 200 appointments; the system does exactly what it was asked to do. No WAF, no static analysis and no scanner detects it, because nothing is malformed. It is only caught by business limits thought up by somebody who knows the domain, and by alerts on business metrics (an anomalous cancellation rate in one tenant). This is why security cannot be delegated entirely to tools.


  1. A chained attack on Nimbus, step by step

Real attacks are not one technique: they are a chain in which each link takes advantage of the previous one. Let us reconstruct a complete one, connecting everything seen in this lesson.

flowchart TB
    A["1. RECONNAISSANCE\nOSINT: an old .env file with a\nnon-expiring API token is found\nin a public repository\n(finding from 01-04)"]
    A --> B["2. VALIDATION\nThe token is still active: nobody\nrotated it or gave it an expiry"]
    B --> C["3. INITIAL ACCESS\nRequests to the production API\nwith a legitimate token:\nno exploit, no alarm"]
    C --> D["4. DISCOVERY\nEndpoint enumeration\nand testing the IDOR in\n/api/v1/reservas/{id}"]
    D --> E["5. COLLECTION\nExport in small batches\nover three weeks\nso as not to draw attention"]
    E --> F["6. ESCALATION\nA signed URL for the attachments\nbucket appears in the data;\nthe attacker tries to widen access"]
    F --> G["7. EXFILTRATION\nAppointment data leaves\ntowards external storage"]
    G --> H["8. IMPACT\nData revealing health information\nout of control + breach notification\n+ loss of trust"]

Where this chain breaks, link by link:

Link Control that breaks it Where it is studied
1 Secret scanning in CI and across the repository history 02-04
2 Mandatory expiry on every token and periodic rotation 02-05
3 Minimal token scope and origin restriction; alert on use from a new IP 02-05, 05-02
4 tenant_id filter and RLS (already fixed in 01-01 and 01-03); unguessable IDs 02-04
5 Rate limiting per token and an alert on anomalous export volume 02-04, 05-02
6 120 s signed URLs (already in place) and a private bucket Already solved in 01-03
7 Egress filtering and detection of anomalous transfers 05-02, 05-04
8 Encryption and minimisation reduce the residual impact Module 3, 06-03

The decisive observation: of the eight links, five break with measures that cost little or nothing — token expiry, minimal scope, tenant filtering, rate limiting and a volume alert. None requires buying a product. And link 6 was already broken in advance thanks to a design decision taken in 01-03: short-lived signed URLs. That is exactly what "defence in depth" means when it works.


  1. Summary table: attack, property affected, detection and defence

Attack CIA property affected Detection signal Main defence Lesson
OSINT — (enabler) Not detectable in your systems Reduce what you publish; rotate exposed secrets 01-04, 02-04
Scanning — (enabler) Bursts of 404s to non-existent routes Reduce surface; alert if a sensitive route stops returning 404 02-04, 05-01
Sniffing / MitM Confidentiality, integrity Unexpected certificate; anomalous ARP TLS with strict verification; encryption internally too 02-04, module 3
DNS spoofing Authenticity Resolutions outside the expected range DNSSEC, controlled resolvers, MFA on the DNS account 05-04
Volumetric DDoS Availability Network saturation with massive traffic The provider's anti-DDoS service; CDN 02-04
Application-layer DDoS Availability High latency with little traffic; one endpoint accounts for the cost Pagination, business limits, rate limiting 02-04, 05-05
Brute force / dictionary Confidentiality Many failures on one account Attempt limits; MFA 02-05
Password spraying Confidentiality Few failures on many accounts, same IP Detection by origin; MFA; passwords not in leaks 02-05
Credential stuffing Confidentiality Isolated successes from distributed IPs MFA; checking against leaked lists 02-05
Session theft / pass-the-cookie Confidentiality, authenticity Token used from a new context with no preceding authentication Secure cookies, short life, revocation, binding to context 02-05
IDOR / broken access control Confidentiality, integrity Accesses where the resource's tenant ≠ the user's tenant Per-resource authorisation; RLS; unguessable IDs 01-01, 01-03
SQL injection All SQL syntax errors in logs; anomalous queries Parameterised queries + least-privilege role 02-04, 05-05
XSS Confidentiality, integrity CSP reports; content with tags in text fields Output encoding + CSP + HttpOnly 02-04, 05-05
CSRF Integrity Actions with an external Origin SameSite; anti-CSRF token 02-04, 02-05
SSRF Confidentiality Outbound requests to internal ranges Allowlist, blocking of internal ranges, egress proxy 02-04, 05-07
Insecure deserialisation All Unexpected execution after receiving data JSON + schema validation; never external pickle 05-05
Sensitive data exposure Confidentiality Responses with extra fields; stack traces in errors Explicit output schemas; generic errors 02-04
Vulnerable dependencies All Composition analysis report Analysis in CI + remediation deadline + SBOM 02-04
Ransomware Availability, confidentiality Lateral movement, backup deletion, mass encryption Immutable backups, MFA, segmentation, EDR 02-04, 04-06
Supply chain All New behaviour after an update Versions pinned by hash, CI without production secrets, SBOM 02-04, 04-04
Business logic abuse Integrity, availability Anomalous business metrics per tenant Business limits; alerts on your own metrics 02-04
Enumeration Confidentiality Sequential walk through IDs; many authenticated 404s UUIDs, uniform responses, rate limiting 02-05

Common Mistakes and Tips

Common mistakes:

  • Studying attacks as a list of names. What has to be retained about each one is three things: which property it breaks, what signal it leaves and which control neutralises it. The name is the least of it.
  • Believing HTTPS solves network attacks. It solves eavesdropping on external content. It does not cover internal traffic in the clear, nor metadata, nor the user who accepts an invalid certificate.
  • Trusting input filtering to a blocklist. Trying to forbid ', <script> or UNION can always be evaded with encodings. The correct defence is structural: parameterise queries and encode on output.
  • Thinking a WAF replaces correct code. A WAF is a useful layer that buys time; it does not fix an injection or a broken access control.
  • Ignoring application-layer DDoS. Money goes into volumetric protection while a single unpaginated endpoint lets someone bring the service down from a laptop.
  • Treating ransomware as an antivirus problem. It is a weeks-long operation whose outcome is decided by immutable backups, MFA and segmentation, not by the signature of the final file.
  • Assuming dependencies are somebody else's problem. 95 % of the code running in production was not written by Iván, and the vulnerabilities in that 95 % are just as exploitable.
  • Forgetting business logic. No tool detects the abuse of a feature that works exactly as it was designed to.

Tips:

  • When you review an endpoint, ask yourself four questions in this order: who are you? (authentication), is it yours? (authorisation), is what you are sending valid? (validation) and how much does this cost? (limits).
  • Search the Nimbus repository for these five patterns: verify=False, SELECT *, string concatenation in SQL, innerHTML and pickle.loads. You will find most of the application risk in under an hour.
  • For every new attack you learn, write the log query that would detect it. If you cannot write it, you will not detect it.
  • Practise the chain exercise: take any incident and ask yourself at which link it would have been cheapest to break it. Almost always it is one of the first.
  • Remember that most successful attacks do not use exploits: they use valid credentials and forgotten configurations.

Exercises

Exercise 1 — Classify and respond to six signals

For each of these extracts or facts observed at Nimbus, state: which attack it suggests, which CIA property is at stake, what you would check next and which defence applies.

(a) 2026-04-02T03:22:10Z auth FAIL user=ivan@... ip=192.0.2.9
    2026-04-02T03:22:14Z auth FAIL user=lucia@... ip=192.0.2.9
    2026-04-02T03:22:18Z auth FAIL user=sara@... ip=192.0.2.9
    2026-04-02T03:22:22Z auth FAIL user=marta@... ip=192.0.2.9
(b) ERROR sqlalchemy.exc.ProgrammingError: syntax error at or near "OR"
    LINE 1: ...end_customers WHERE tenant_id = 42 AND name LIKE '%' OR '1'='1...
(c) GET /api/v1/reservas/10001  200
    GET /api/v1/reservas/10002  200
    GET /api/v1/reservas/10003  200
    GET /api/v1/reservas/10004  200
    ... 4,800 requests in 40 minutes, same token, 200 responses
(d) The Nimbus API records 34 requests to /api/v1/informes/ocupacion
    in 90 seconds. PostgreSQL CPU is at 100 %. Total inbound traffic
    is 0.4 Mbps.
(e) The API server has made outbound requests to
    http://169.254.169.254/latest/meta-data/iam/security-credentials/
    right after a clinic configured the URL of its logo.
(f) A routine deployment changes a GitHub Actions workflow step
    referenced as @v3. After the deployment, the workflow makes an
    outbound request to a domain never seen before.

Exercise 2 — Fix three vulnerable fragments

The following fragments are in the Nimbus code. For each one: identify the vulnerability, explain how it would conceptually be exploited, rewrite the fixed code and add a second layer of defence independent of the code.

# (a) Customer search box in the clinic panel
@app.get("/api/v1/clientes/buscar")
def search(q: str, current_user = Depends(get_current_user)):
    sql = f"SELECT * FROM end_customers WHERE tenant_id={current_user.tenant_id} " \
          f"AND name LIKE '%{q}%'"
    return [dict(r) for r in db.execute(sql).fetchall()]
# (b) Downloading a booking's attachment
@app.get("/api/v1/adjuntos/{key}")
def attachment(key: str, current_user = Depends(get_current_user)):
    return storage.download(f"nimbus-adjuntos-prod/{key}")
# (c) Receiving the payment gateway webhook
@app.post("/api/v1/webhooks/pago")
def webhook(event: dict):
    if event["status"] == "paid":
        db.execute("UPDATE invoices SET status='paid' WHERE id=:id",
                   {"id": event["invoice_id"]})
    return {"ok": True}

Exercise 3 — Reconstruct and break a chain

Nimbus suffers the following incident:

  1. Rubén receives an e-mail with an attachment claiming to be an issue report from a customer clinic. He opens it.
  2. His laptop runs a component that establishes an outbound channel to the attacker.
  3. The attacker extracts from Rubén's browser the session cookie for the internal support panel, which does not expire for 30 days.
  4. With that cookie they enter the support panel without going through the log-in or the MFA.
  5. From the support panel, which offers a "view as customer" feature, they reach the data of 40 clinics.
  6. They export a CSV with 120,000 appointment records using the panel's export feature.
  7. Three days later, the export appears published on a forum.

Required: (a) assign each step to its Kill Chain phase and to the technique from this lesson's catalogue; (b) give two controls per step that would have broken it; (c) explain why MFA did not protect at step 4 and which concrete measure would have done; (d) say at which step detection would have been easiest and what exact signal you would have looked for.


Solutions

Solution 1

Case Attack Property What to check Defence
(a) Password spraying: one attempt per account, many accounts, same IP, in the small hours Confidentiality Whether there was any auth OK from that IP; whether those accounts have MFA; whether the IP appears in other logs Mandatory MFA; detection by origin (not by account); progressive blocking of the IP; passwords checked against leaked lists
(b) SQL injection: the error reveals that the user's input altered the syntax All Which endpoint generated it, which parameter, whether any variant succeeded (200 responses with anomalous volume), what permissions the DB role has Parameterise the query; nimbus_api role with no DELETE and no DDL; generic errors towards the client; alert on SQL syntax errors
(c) Identifier enumeration with IDOR: sequential IDs returning 200 Confidentiality Whether the IDs belong to several tenants (if so, access control is broken); which token it is and who owns it tenant_id filter and RLS; UUID identifiers; rate limiting per token; alert on the number of distinct resources accessed per session
(d) Application-layer DDoS: enormous cost with derisory traffic Availability What date range those requests ask for; whether they come from a single token or tenant Mandatory pagination, range limit, rate limiting, maximum query time in PostgreSQL
(e) SSRF pointing at the instance metadata service; objective: temporary credentials for A-05 Confidentiality (serious: it can escalate to everything) Whether the request succeeded and whether those credentials have been used from outside; review the cloud account log URL validation blocking internal ranges and without redirects; egress proxy with an allowlist; require the metadata service version that demands a token
(f) Supply chain via a CI action referenced by a movable tag All Which secrets were accessible in that workflow; what was sent to the new domain; rotate everything exposed immediately Pin actions by commit hash; workflow without access to production secrets; short-lived credentials; egress filtering on the runner

Solution 2

(a) Customer search box. Vulnerabilities: SQL injection through concatenation and excessive exposure through SELECT *. Conceptual exploitation: a value of q containing a quotation mark closes the string and allows conditions to be added, turning the filter into an always-true condition and returning the full contents of the table accessible to the role.

@app.get("/api/v1/clientes/buscar")
@limiter.limit("30/minute")
def search(q: str, current_user = Depends(get_current_user)):
    if len(q) < 3:
        raise HTTPException(400, "Enter at least 3 characters")
    rows = db.execute(
        """SELECT id, name, email
             FROM end_customers
            WHERE tenant_id = :t AND name ILIKE :pattern
            ORDER BY name LIMIT 50""",
        {"t": current_user.tenant_id, "pattern": f"%{q}%"}).fetchall()
    return [dict(r) for r in rows]

Second layer, independent of the code: the nimbus_api role with no DELETE and no DDL (01-03) and the RLS by tenant_id, which prevents other tenants' rows being returned even if the query is manipulated. The LIMIT 50 additionally adds protection against mass extraction, and the three-character minimum avoids the empty search that returns the whole database.

(b) Attachment download. Vulnerability: broken access control — any key in the bucket is downloaded without checking that the booking belongs to the user's tenant. It can also allow path traversal if the key contains ../. Conceptual exploitation: with a key known or guessed, any authenticated user downloads another clinic's attachment: a scanned medical report.

@app.get("/api/v1/adjuntos/{booking_id}")
def attachment(booking_id: int, current_user = Depends(get_current_user)):
    row = db.execute(
        "SELECT attachment_key FROM bookings WHERE id=:id AND tenant_id=:t",
        {"id": booking_id, "t": current_user.tenant_id}).fetchone()
    if row is None or row.attachment_key is None:
        raise HTTPException(404)
    url = generate_signed_url("nimbus-adjuntos-prod", row.attachment_key,
                              expiry_seconds=120)
    record_audit(current_user.id, "attachment_download", booking_id)
    return {"url": url}

Key changes: the client no longer chooses the object key but the identifier of their booking; the real key is obtained from the database after ownership has been verified; a 120 s signed URL is handed over instead of serving the object; and the access is logged. Second layer: the bucket is private, the policy prevents anonymous access, and the API has no delete permission over it (01-03).

(c) Payment webhook. Vulnerabilities: spoofing (there is no check that the message comes from the gateway) and integrity tampering (anyone can mark invoices as paid). Conceptual exploitation: an HTTP request to that endpoint with a suitable JSON body turns an unpaid invoice into a paid one. It is the S threat of flow F8 in the 01-04 DFD.

import hmac, hashlib
from fastapi import Request, HTTPException

@app.post("/api/v1/webhooks/pago")
async def webhook(request: Request):
    body = await request.body()
    received_signature = request.headers.get("X-Pasarela-Firma", "")
    expected_signature = hmac.new(WEBHOOK_SECRET, body, hashlib.sha256).hexdigest()

    # Constant-time comparison: prevents deducing the signature by measuring timings
    if not hmac.compare_digest(received_signature, expected_signature):
        record_audit(None, "webhook_invalid_signature", request.client.host)
        raise HTTPException(401)

    event = json.loads(body)
    if event["tipo"] != "pago.confirmado":
        return {"ok": True}
    # Idempotency: the same event may arrive twice
    db.execute("""INSERT INTO gateway_events (event_id) VALUES (:e)
                  ON CONFLICT DO NOTHING""", {"e": event["id"]})
    ...

Second layer: confirm the status against the gateway's API before accepting a financial operation as valid, and restrict the origin to the IPs published by the provider. HMAC is explained in detail in 03-04.

Solution 3

(a) and (b) Chain, techniques and controls:

Step Phase Technique Control 1 Control 2
1. E-mail with attachment Delivery Phishing with an attachment (02-03) Mail filtering with attachment analysis and isolation External e-mail banner + training and a reporting channel
2. Execution and outbound channel Exploitation + Command and control Execution on the endpoint EDR with behavioural detection Egress filtering: only permitted destinations from laptops
3. Browser cookie theft Credential Access Pass-the-cookie Short-lived sessions (hours, not 30 days) Encryption of the browser store + a reauthenticated session policy
4. Use of the cookie Initial access to the application Session theft Binding the session to the context (IP/device) and reauthentication on change Alert on an active session with no preceding authentication event
5. "View as customer" in the support panel Escalation / Privilege Escalation Administrative function without control Reauthentication and mandatory justification to impersonate; named logging Approval by a second operator and just-in-time access (02-05)
6. Export of 120,000 records Collection Mass export Volume limit per export and per day Immediate alert on any export above a threshold
7. Publication Impact Disclosure Encryption and minimisation reduce the residual damage Response and notification plan (04-05, 06-03)

(c) Why MFA did not protect. MFA is verified at the moment of log-in and produces a session. If the attacker steals the session already issued, they never go through authentication again: they walk straight in with a valid artefact. That is the essence of pass-the-cookie and the reason why "we have MFA" is not a complete answer.

What would have worked, in order of effectiveness:

  1. Binding the session to a context (device, IP or client certificate), so that a cookie used from another machine is invalidated.
  2. Short sessions with silent renewal: a 30-day cookie is a permanent credential in disguise. Hours, not weeks.
  3. Reauthentication for sensitive operations: impersonating a customer or exporting data requires presenting the second factor again, even if the session is valid.
  4. Cryptographically binding the token to the client (token binding / device-bound keys), which is the direction passkeys point in (02-05).

(d) Where to detect and what signal to look for. The easiest point is step 6: an export of 120,000 records spanning 40 different tenants is a quantitatively unique event in Nimbus's normal operation. The detection query is straightforward on the append-only audit table from 01-01:

-- Anomalous exports: more than 5,000 records or more than 3 distinct
-- tenants touched by the same session within an hour
SELECT session_id, user_id,
       COUNT(DISTINCT tenant_id) AS tenants,
       SUM(record_count)         AS records
  FROM access_audit
 WHERE action = 'export'
   AND ts > now() - interval '1 hour'
 GROUP BY session_id, user_id
HAVING SUM(record_count) > 5000
    OR COUNT(DISTINCT tenant_id) > 3;

What matters about this query is not its SQL but its premise: it only works if the audit trail records session_id, tenant_id and record_count. Detection is not improvised when the incident happens; it is designed when the log is written. A second useful signal, and an earlier one, is step 4: an active session with no preceding authentication event in the log is an anomaly with no legitimate explanation.


Conclusion

You now know the adversary in action. You have worked through the attacks ordered by the phases of the chain: the reconnaissance that happens without touching your systems — OSINT in certificates, repositories and job adverts — and the scanning that does leave a trace, with the practical lesson that the useful alert is not "we are being scanned" but "a sensitive route has stopped returning 404". You have seen the network attacks — eavesdropping, ARP and DNS spoofing, interception — and why they still matter in a world with HTTPS: because of internal traffic in the clear, because of metadata, and because of the verify=False somebody left in production. You have distinguished volumetric denial of service, whose defence is bought, from application-layer denial of service, whose defence is programmed, and you have seen that twenty well-chosen requests do more damage than a botnet when pagination is missing.

In credentials you have learned to tell apart brute force, dictionary attacks, password spraying — designed precisely to get around per-account lockout — credential stuffing and session theft, with the uncomfortable idea that a valid credential sets off no alarm. In web applications you have worked through the OWASP Top 10 on the Nimbus API: broken access control and IDOR, SQL injection and why parameterisation works where manual escaping fails, XSS with its three layers of defence, CSRF, SSRF and its route towards the cloud account's credentials, insecure deserialisation, data exposure and vulnerable dependencies. You have understood modern ransomware as a three-week operation whose outcome is decided by immutable backups, not by the antivirus; supply chain attacks, where everything looks legitimate; and API attacks, with the business logic abuse that no tool detects. And you have reconstructed a complete chain on Nimbus, confirming that five of its eight links break with measures that cost no money at all.

What is still missing is the vector that heads almost every set of statistics and that appears in none of the previous techniques: the person. In the next lesson, Social Engineering and Phishing (02-03), we will see why the human factor is the dominant way in, which principles of influence attackers exploit, the complete catalogue of techniques — from mass phishing to CEO fraud aimed at Sara, vishing, quishing and consent phishing —, how to take a malicious e-mail apart indicator by indicator by reading its headers, and the technical and process defences that counter it, starting with SPF, DKIM and DMARC.

Fundamentals of Information Security Course

Module 1: Introduction to Information Security

Module 2: Cybersecurity

Module 3: Cryptography

Module 4: Risk Management and Protection Measures

Module 5: Security Tools and Techniques

Module 6: Best Practices and Regulations

Module 7: Final Project

© Copyright 2026. All rights reserved