EthicsComputerCourses
Join training +91 90532 10052

Project: Incident Response Tabletop Exercise

Cyber Security · Lesson 44 of 44 Advanced 9 min read Updated

Incident response plans look good on paper until the first real incident shows that nobody knows who phones CERT-In, the backup admin is on leave and the only copy of the contact list is on the encrypted file server. A tabletop exercise finds those gaps safely: the team sits around a table (or joins a video call), a facilitator describes a realistic incident step by step, and the team talks through what they would do. No real system is touched.

In this project you will plan a complete ransomware tabletop for a fictional diagnostic lab chain in Kochi, write the scenario and its timed injects (new pieces of information), analyse an evidence inject, record the team's decisions, check them against the playbook and the CERT-In 6-hour deadline, and build a Python program that produces the after-action report. It brings together Incident Response, Backups, Ransomware Resilience & Recovery and Ethics & Indian Cyber Law.

Step 1: plan the exercise

Every exercise starts with a short plan approved by leadership. The objectives decide everything else, so make them specific and testable:

YAML
exercise: Ransomware tabletop, Sahyog Diagnostics (fictional), Kochi
date: 2026-12-09, 09:00 to 12:00 IST (scenario time runs faster than real time)
sponsor: Meera Nair, Lab Director
facilitator: Farhan Sheikh (security lead), not a player
players: IT manager, network admin, lab operations head, legal adviser, communications, CERT-In point of contact
objectives:
  - Contain a ransomware outbreak on the file server within 30 minutes of detection
  - Decide who reports to CERT-In and send the report within 6 hours of noticing
  - Find a clean restore point using the evidence
  - Agree a holding statement for media and patients
ground_rules:
  - No-fault: we test the plan, not the people
  - Discussion only; no live systems are touched
  - Decisions are made with the information given so far
  - "Parking lot" for side questions, answered after the exercise
outputs: decision log, after-action report with owners and dates

Choose a scenario that matches real threats to the organisation. For a diagnostic lab that stores patient reports, ransomware with data theft is a realistic and serious case. Keep the company fictional so nobody feels exposed, and make sure the facilitator knows the incident response plan well.

Step 2: write the scenario and injects

The scenario starts with something small and gets worse over time, the way real incidents do. Each inject has a scenario time, a source and the information it adds:

PythonRun it yourself
INJECTS = [
    ("09:00", "Facilitator", "Lab technicians cannot open report PDFs on the shared drive."),
    ("09:10", "Helpdesk", "Report files now end in .crypt; a ransom note is in every folder."),
    ("09:25", "SOC", "EDR alert: mass file renames by account svc-backup on LIS-FILES-01."),
    ("09:40", "Evidence", "VPN log excerpt handed to the team."),
    ("10:30", "Media", "A local reporter asks whether patient reports were leaked."),
    ("11:15", "Attacker", "A second note claims patient data was copied before encryption."),
]

for time, source, text in INJECTS:
    print(f"{time}  [{source:<11}] {text}")
Output
09:00  [Facilitator] Lab technicians cannot open report PDFs on the shared drive.
09:10  [Helpdesk   ] Report files now end in .crypt; a ransom note is in every folder.
09:25  [SOC        ] EDR alert: mass file renames by account svc-backup on LIS-FILES-01.
09:40  [Evidence   ] VPN log excerpt handed to the team.
10:30  [Media      ] A local reporter asks whether patient reports were leaked.
11:15  [Attacker   ] A second note claims patient data was copied before encryption.

For each inject, the facilitator prepares two or three questions to keep the discussion moving:

InjectQuestions for the team
09:10 ransom noteIs this an incident? Who leads? Who is called first, and how, if email may be compromised?
09:25 EDR alertWhat do you isolate, and how, without destroying evidence? What can the svc-backup account reach?
09:40 VPN logWhen did the attacker get in? What does that mean for which backup you can trust?
10:30 reporterWho may speak to the media? What can be said when the facts are not known?
11:15 data theft claimIs this now a personal data breach? Who must be told, and by when?

Prepare a few contingency injects too, such as "the backup administrator is on leave and unreachable", to use if the team moves too fast or too smoothly.

Step 3: analyse the evidence inject

Good tabletops include real analysis, not only talk. At 09:40 the team receives a VPN log excerpt. The office network is 198.51.100.0/24; successful logins from anywhere else deserve a closer look:

PythonRun it yourself
import ipaddress

OFFICE_NET = ipaddress.ip_network("198.51.100.0/24")
VPN_LOG = """2026-12-08 18:02 user=priya src=198.51.100.23 result=success
2026-12-08 22:11 user=svc-backup src=203.0.113.50 result=fail
2026-12-08 22:12 user=svc-backup src=203.0.113.50 result=fail
2026-12-08 22:14 user=svc-backup src=203.0.113.50 result=success
2026-12-09 02:40 user=svc-backup src=203.0.113.50 result=success
2026-12-09 08:55 user=rohan src=198.51.100.41 result=success"""

for line in VPN_LOG.splitlines():
    date, time, user, src, result = line.split()
    ip = ipaddress.ip_address(src.split("=")[1])
    if result == "result=success" and ip not in OFFICE_NET:
        print(f"{date} {time}  {user.split('=')[1]} logged in from {ip} (outside office network)")
Output
2026-12-08 22:14  svc-backup logged in from 203.0.113.50 (outside office network)
2026-12-09 02:40  svc-backup logged in from 203.0.113.50 (outside office network)

The analysis changes the team's decisions. A service account should never log in to the VPN at all, two failures followed by a success suggest guessed or stolen credentials, and the attacker was inside from 22:14 on 8 December. So any backup taken after that time is suspect, and the team must look for a restore point before it. This is the same reasoning you practised in Detection Engineering and Identity Security.

Step 4: record decisions and watch the clocks

A scribe (not the facilitator) writes down every decision with its scenario time. Two legal clocks start when the incident is noticed, here at 09:10 when the ransom note is reported:

  • CERT-In: report within 6 hours, so by 15:10.
  • DPDP Act 2023: the breach duties under the DPDP Rules 2025 apply from 13 May 2027. In a real incident after that date, a personal data breach such as stolen patient reports must be intimated to the Data Protection Board and to affected people without delay, with a detailed report to the Board within 72 hours. The exercise is in December 2026, but rehearse this step anyway so the process is ready in time.
PythonRun it yourself
from datetime import datetime, timedelta

noticed = datetime(2026, 12, 9, 9, 10)
DECISIONS = [
    ("09:32", "isolate", "Network team pulled LIS-FILES-01 off the switch port"),
    ("09:55", "leadership", "Lab director Meera Nair and legal adviser briefed by phone"),
    ("10:20", "disable", "svc-backup disabled; VPN sessions killed"),
    ("10:45", "comms", "Holding statement approved: investigating, no details yet"),
    ("11:50", "backup", "Backup of 8 Dec 18:00 confirmed clean and isolated"),
    ("14:40", "certin", "Report emailed to CERT-In with the facts known so far"),
]

print("CERT-In report due:", (noticed + timedelta(hours=6)).strftime("%d %b %H:%M"))
for time, key, text in DECISIONS:
    print(f"  {time}  {key:<10} {text}")
Output
CERT-In report due: 09 Dec 15:10
  09:32  isolate    Network team pulled LIS-FILES-01 off the switch port
  09:55  leadership Lab director Meera Nair and legal adviser briefed by phone
  10:20  disable    svc-backup disabled; VPN sessions killed
  10:45  comms      Holding statement approved: investigating, no details yet
  11:50  backup     Backup of 8 Dec 18:00 confirmed clean and isolated
  14:40  certin     Report emailed to CERT-In with the facts known so far

Notice what the team did not do: nobody mentioned preserving memory or logs before the file server is rebuilt. Gaps like this are the main output of a tabletop.

Step 5: score decisions against the playbook

The ransomware playbook lists the expected actions and how many minutes after the incident is noticed each should be done. Comparing the decision log with it gives an objective score:

PythonRun it yourself
from datetime import datetime, timedelta

def at(hhmm):
    return datetime.strptime(f"2026-12-09 {hhmm}", "%Y-%m-%d %H:%M")

PLAYBOOK = [
    ("isolate", "Isolate the file server", 30),
    ("disable", "Disable the compromised account", 45),
    ("preserve", "Preserve memory and logs", 120),
    ("certin", "Report to CERT-In", 360),
]
done = {"isolate": at("09:32"), "disable": at("10:20"), "certin": at("14:40")}
noticed = at("09:10")

for key, task, minutes in PLAYBOOK:
    due = noticed + timedelta(minutes=minutes)
    when = done.get(key)
    status = "MISSED" if when is None else "MET" if when <= due else "LATE"
    print(f"{status:<6} {task:<32} due {due:%H:%M}")
Output
MET    Isolate the file server          due 09:40
LATE   Disable the compromised account  due 09:55
MISSED Preserve memory and logs         due 11:10
MET    Report to CERT-In                due 15:10

Scores are a starting point for discussion, not a grade. "Late" on disabling the account may reveal that nobody knew who owns service accounts, which is a process fix, not a person's fault.

Step 6: draft the CERT-In report

Teams often lose time writing the first report. Prepare a template in advance and practise filling it in during the exercise. Always use the current incident reporting form and contact details on the CERT-In website (cert-in.org.in); the fields below show the kind of information such a report needs, not the official form:

Text
Organisation:          Sahyog Diagnostics (fictional), Kochi
Point of contact:      CERT-In PoC name, phone, email
Incident type:         Ransomware with claimed data theft
Noticed at:            09 Dec 2026, 09:10 IST
First known activity:  08 Dec 2026, 22:14 IST (VPN login, service account)
Systems affected:      LIS-FILES-01 (report file server); others under investigation
Indicators:            Source IP 203.0.113.50; file extension .crypt
Actions taken:         Server isolated 09:32; account disabled 10:20; clean backup identified
Status:                Investigation ongoing; updates to follow

Report what you know when the deadline arrives, mark what is still unknown, and send updates later. Waiting for complete facts is a common reason organisations miss the 6-hour window.

Step 7: the complete after-action report generator

Now combine everything into one program that builds the after-action report: a merged timeline of injects, log evidence and decisions, the key findings with the CERT-In deadline, the playbook score and the list of improvement actions:

PythonRun it yourself
import ipaddress
from datetime import datetime, timedelta

DAY = "2026-12-09"
OFFICE_NET = ipaddress.ip_network("198.51.100.0/24")

def at(hhmm, day=DAY):
    return datetime.strptime(f"{day} {hhmm}", "%Y-%m-%d %H:%M")

INJECTS = [
    ("09:00", "Facilitator", "Lab technicians cannot open report PDFs on the shared drive."),
    ("09:10", "Helpdesk", "Report files now end in .crypt; a ransom note is in every folder."),
    ("09:25", "SOC", "EDR alert: mass file renames by account svc-backup on LIS-FILES-01."),
    ("09:40", "Evidence", "VPN log excerpt handed to the team."),
    ("10:30", "Media", "A local reporter asks whether patient reports were leaked."),
    ("11:15", "Attacker", "A second note claims patient data was copied before encryption."),
]

VPN_LOG = """2026-12-08 18:02 user=priya src=198.51.100.23 result=success
2026-12-08 22:11 user=svc-backup src=203.0.113.50 result=fail
2026-12-08 22:12 user=svc-backup src=203.0.113.50 result=fail
2026-12-08 22:14 user=svc-backup src=203.0.113.50 result=success
2026-12-09 02:40 user=svc-backup src=203.0.113.50 result=success
2026-12-09 08:55 user=rohan src=198.51.100.41 result=success"""

PLAYBOOK = [
    ("isolate", "Isolate LIS-FILES-01 from the network", 30),
    ("leadership", "Brief leadership and legal", 60),
    ("disable", "Disable svc-backup and revoke its VPN sessions", 45),
    ("preserve", "Preserve memory and logs before any rebuild", 120),
    ("comms", "Approve a holding statement for media and patients", 120),
    ("backup", "Confirm a clean backup from before the first bad login", 240),
    ("certin", "Report the incident to CERT-In", 360),
]

DECISIONS = [
    ("09:32", "isolate", "Network team pulled LIS-FILES-01 off the switch port"),
    ("09:55", "leadership", "Lab director Meera Nair and legal adviser briefed by phone"),
    ("10:20", "disable", "svc-backup disabled; VPN sessions killed"),
    ("10:45", "comms", "Holding statement approved: investigating, no details yet"),
    ("11:50", "backup", "Backup of 8 Dec 18:00 confirmed clean and isolated"),
    ("14:40", "certin", "Report emailed to CERT-In with the facts known so far"),
]

def suspicious_logins(log):
    found = []
    for line in log.splitlines():
        date, time, user, src, result = line.split()
        ip = ipaddress.ip_address(src.split("=")[1])
        if result == "result=success" and ip not in OFFICE_NET:
            found.append((at(time, date), user.split("=")[1], str(ip)))
    return found

def score(noticed, decisions, playbook):
    done = {key: (at(t), text) for t, key, text in decisions}
    rows = []
    for key, task, minutes in playbook:
        due = noticed + timedelta(minutes=minutes)
        if key not in done:
            rows.append(("MISSED", task, due, None))
        else:
            when = done[key][0]
            rows.append(("MET" if when <= due else "LATE", task, due, when))
    return rows

noticed = at("09:10")
bad = suspicious_logins(VPN_LOG)
first_access = bad[0][0]
rows = score(noticed, DECISIONS, PLAYBOOK)

lines = [
    "AFTER-ACTION REPORT: Ransomware tabletop, Sahyog Diagnostics (fictional)",
    f"Exercise date: {DAY}   Incident noticed: {noticed:%H:%M}",
    "",
    "TIMELINE",
]
events = [(at(t), f"[inject] {src}: {text}") for t, src, text in INJECTS]
events += [(when, f"[log] VPN login {user} from {ip} (outside office network)") for when, user, ip in bad]
events += [(at(t), f"[decision] {text}") for t, _, text in DECISIONS]
for when, text in sorted(events):
    lines.append(f"  {when:%d %b %H:%M}  {text}")

lines += ["", "KEY FINDINGS",
          f"  Attacker access began {first_access:%d %b %H:%M}, {noticed - first_access} before the incident was noticed.",
          f"  CERT-In deadline (6 hours from noticing): {noticed + timedelta(hours=6):%d %b %H:%M}",
          "", "PLAYBOOK SCORE"]
for status, task, due, when in rows:
    done_at = f"done {when:%H:%M}" if when else "not done"
    lines.append(f"  {status:<6} {task:<55} due {due:%H:%M}, {done_at}")
met = sum(1 for r in rows if r[0] == "MET")
lines += [f"  {met} of {len(rows)} actions met on time", "", "ACTIONS"]
for status, task, due, when in rows:
    if status != "MET":
        lines.append(f"  - {task}: agree an owner and a date to improve this step")
print("\n".join(lines))
Output
AFTER-ACTION REPORT: Ransomware tabletop, Sahyog Diagnostics (fictional)
Exercise date: 2026-12-09   Incident noticed: 09:10

TIMELINE
  08 Dec 22:14  [log] VPN login svc-backup from 203.0.113.50 (outside office network)
  09 Dec 02:40  [log] VPN login svc-backup from 203.0.113.50 (outside office network)
  09 Dec 09:00  [inject] Facilitator: Lab technicians cannot open report PDFs on the shared drive.
  09 Dec 09:10  [inject] Helpdesk: Report files now end in .crypt; a ransom note is in every folder.
  09 Dec 09:25  [inject] SOC: EDR alert: mass file renames by account svc-backup on LIS-FILES-01.
  09 Dec 09:32  [decision] Network team pulled LIS-FILES-01 off the switch port
  09 Dec 09:40  [inject] Evidence: VPN log excerpt handed to the team.
  09 Dec 09:55  [decision] Lab director Meera Nair and legal adviser briefed by phone
  09 Dec 10:20  [decision] svc-backup disabled; VPN sessions killed
  09 Dec 10:30  [inject] Media: A local reporter asks whether patient reports were leaked.
  09 Dec 10:45  [decision] Holding statement approved: investigating, no details yet
  09 Dec 11:15  [inject] Attacker: A second note claims patient data was copied before encryption.
  09 Dec 11:50  [decision] Backup of 8 Dec 18:00 confirmed clean and isolated
  09 Dec 14:40  [decision] Report emailed to CERT-In with the facts known so far

KEY FINDINGS
  Attacker access began 08 Dec 22:14, 10:56:00 before the incident was noticed.
  CERT-In deadline (6 hours from noticing): 09 Dec 15:10

PLAYBOOK SCORE
  MET    Isolate LIS-FILES-01 from the network                   due 09:40, done 09:32
  MET    Brief leadership and legal                              due 10:10, done 09:55
  LATE   Disable svc-backup and revoke its VPN sessions          due 09:55, done 10:20
  MISSED Preserve memory and logs before any rebuild             due 11:10, not done
  MET    Approve a holding statement for media and patients      due 11:10, done 10:45
  MET    Confirm a clean backup from before the first bad login  due 13:10, done 11:50
  MET    Report the incident to CERT-In                          due 15:10, done 14:40
  5 of 7 actions met on time

ACTIONS
  - Disable svc-backup and revoke its VPN sessions: agree an owner and a date to improve this step
  - Preserve memory and logs before any rebuild: agree an owner and a date to improve this step

The generator keeps all the exercise data in plain lists at the top, so you can run the same program for next quarter's scenario by changing only the data. The timeline also makes a powerful point for leadership: the attacker was inside for almost 11 hours before anyone noticed.

Step 8: run the debrief

Hold a short hot wash immediately after the exercise while memories are fresh: what went well, what was hard, what surprised you. Then, within a week, the facilitator sends the after-action report. For each gap, agree an action with an owner and a date, for example:

  • Add "preserve memory and logs" as a step before any rebuild in the ransomware playbook, with the commands from Incident Response attached.
  • Give every service account a named owner and block VPN logins for service accounts.
  • Print the CERT-In report template and contact list and keep a copy outside the main systems.
  • Rehearse the DPDP breach intimation steps before May 2027.

Track these actions like vulnerabilities, and run the next exercise in three to six months with a different scenario, such as a compromised email account or a supplier breach, to test whether the fixes worked.

Ideas to extend the project

  • Load injects, playbook and decisions from JSON files so non-programmers can write scenarios.
  • Add contingency injects that the facilitator can trigger, and record which were used.
  • Produce the report as Markdown or HTML, with every value escaped.
  • Add a section that lists parking-lot questions and their answers.
  • Write a second scenario for business email compromise in the finance team.

Key points

  • A tabletop exercise tests the plan, people and process through discussion only; no live system is touched.
  • Start with an approved plan: specific objectives, roles, a neutral facilitator, a scribe and no-fault ground rules.
  • Escalating, timed injects with prepared questions keep the exercise realistic; include real evidence to analyse.
  • Record every decision with its time and check it against the playbook and the CERT-In 6-hour deadline.
  • Prepare the CERT-In report template in advance and report what you know by the deadline.
  • Finish with a hot wash and an after-action report whose actions have owners and dates.

Exercise

Add a contingency inject at 10:05 from HR saying "The backup administrator is on leave and not reachable.", and a decision at 10:10 with key backup_contact saying "Deputy admin Kabir called in from home". Add a playbook entry ("backup_contact", "Reach a second person with backup access", 90). Run the complete program and check that the new inject and decision appear in the timeline and that the score becomes 6 of 8.

Show solution
Python
# Add these three lines straight after PLAYBOOK, DECISIONS and INJECTS are defined
INJECTS.append(("10:05", "HR", "The backup administrator is on leave and not reachable."))
DECISIONS.append(("10:10", "backup_contact", "Deputy admin Kabir called in from home"))
PLAYBOOK.append(("backup_contact", "Reach a second person with backup access", 90))

Adding three items to the lists is all it takes: the timeline is sorted by time, so the HR inject at 10:05 and Kabir's call at 10:10 fall into place automatically. The new action is due at 10:40 (90 minutes after 09:10) and was done at 10:10, so it is met and the score becomes 6 of 8. In the complete program you would add the items directly to INJECTS, DECISIONS and PLAYBOOK.

Quick Quiz

1. What is the main purpose of a tabletop exercise?

2. In the scenario, why does the VPN log change the choice of backup?

3. The incident was noticed at 09:10. By when must the CERT-In report be sent?

4. What should a team do if the 6-hour deadline arrives before all facts are known?

Learn Cyber Security with a mentor

Free lessons get you started. Our Ethical Hacking & Cyber Security training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call