Project: Incident Response Tabletop Exercise
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:
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 datesChoose 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:
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}")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:
| Inject | Questions for the team |
|---|---|
| 09:10 ransom note | Is this an incident? Who leads? Who is called first, and how, if email may be compromised? |
| 09:25 EDR alert | What do you isolate, and how, without destroying evidence? What can the svc-backup account reach? |
| 09:40 VPN log | When did the attacker get in? What does that mean for which backup you can trust? |
| 10:30 reporter | Who may speak to the media? What can be said when the facts are not known? |
| 11:15 data theft claim | Is 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:
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)")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.
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}")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:
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}")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:
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 followReport 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:
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))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
# 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?
Tabletops are no-fault rehearsals; no live system is touched.
2. In the scenario, why does the VPN log change the choice of backup?
Restore from a point before the first known attacker activity.
3. The incident was noticed at 09:10. By when must the CERT-In report be sent?
CERT-In's directions require reporting within 6 hours of noticing the incident.
4. What should a team do if the 6-hour deadline arrives before all facts are known?
An initial report with updates is better than a missed deadline.
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
