Scenario Framework
The tutorial framework uses typed Python classes to declare everything that appears in the lab: users, network segments, mock services, and complete hosts. A scenario generator combines these declarations with a tutorial’s requirements and produces a ready-to-run session.
This page explains how to read, write, and extend the framework. For the story and infrastructure of the existing scenario see Logfile Inc. — Scenario Guide.
Overview
flowchart TD
user["User\n(mmorgan, sking, …)"]
segment["Segment\n(ApplicationServers, …)"]
service["Service\n(SSHService, SFTPService, …)"]
host["Host\n(Web01, Router01, …)"]
scenario["Scenario\n(LogfileIncScenario)"]
req["Requirement\n(RandomPassword, RandomKeyPair, …)"]
gen["ScenarioGenerator.build()"]
session["ScenarioSession"]
user --> host
segment --> host
service --> host
host --> scenario
req --> gen
scenario --> gen
gen --> session
Core concepts
- User
A person in the assessment scenario. Carries
username,full_name, androle. Used as a type (class reference), not as an instance.- Segment
A network segment. Provides
nameandsubnetfor documentation and topology validation.- Service
A single network service on a host. Built-in types:
SSHService,SFTPService,HTTPService,SNMPService,PostgreSQLService. Each carries aportand aprotocolclass variable.- Host
A mock server. Declares topology (
label,hostname,address,segment,users,services) and behaviour methods (shell_outputs,sftp_files,exec_outputs,shell_prompt). Dynamic values are injected viaconfigure(session_data).- Scenario
Groups the hosts and users that belong to one engagement.
- Requirement
Describes what dynamic data a tutorial needs. Has two phases:
generate()— produces values merged into the session dataapply(hosts, values)— pushes generated values into host instances
- ScenarioSession
The live, configured state for one tutorial run. Holds host instances, the asyncio event queue, and all generated values. Exposes
template_vars()for step command/hint interpolation.- ScenarioGenerator
build(scenario, host_aliases, requires, sshmitm_port)instantiates hosts, runs all requirements, and returns aScenarioSession.
Adding a new host
Create a sub-package under
sshmitm/tutorial/hosts/<scenario_name>/<hostname>/:
sshmitm/tutorial/hosts/logfile_inc/
new_server/
__init__.py
Minimal __init__.py:
from sshmitm.tutorial.hosts import Host, SSHService
from sshmitm.tutorial.hosts.logfile_inc import ApplicationServers, MaxMorgan
class NewServer(Host):
label = "newserver"
hostname = "newserver.logfileinc.internal"
address = "127.2.0.10"
segment = ApplicationServers
users = [MaxMorgan]
services = [SSHService(port=20022)]
def configure(self, session_data: dict) -> None:
# Accept session values like passwords or authorized keys
pw = session_data.get(f"newserver_{MaxMorgan.username}_password")
if pw:
self._password = str(pw)
def shell_outputs(self, session_data: dict) -> dict[str, bytes]:
return {"whoami": b"mmorgan\r\n"}
Then add the host to LogfileIncScenario.all_hosts() and declare an
entry point in pyproject.toml:
[project.entry-points."sshmitm.Host"]
newserver = "sshmitm.tutorial.hosts.logfile_inc.new_server:NewServer"
Requirement types
Class |
Purpose |
|---|---|
|
Generates a random password and stores it under
|
|
Uses a fixed password. Useful when step content must reference a known value. |
|
Generates a fresh ECDSA key pair. Stores the private key under
|
|
Same as |
|
A random hex string, not tied to any host. Useful for SNMP community strings, OTP tokens, etc. |
|
Picks one value from a list at runtime. |
|
Stores any fixed value in the session data. |
Writing a tutorial that uses the framework
A tutorial declares which hosts it needs and what dynamic data each host
requires. The ScenarioGenerator does the rest.
Example — a tutorial that intercepts a password login to web01:
from sshmitm.tutorial._definitions import Step, Tutorial
from sshmitm.tutorial._conditions import PortOpen, UserInput, TRUE
from sshmitm.tutorial._requirements import RandomPassword
from sshmitm.tutorial._session import ScenarioGenerator
from sshmitm.tutorial.hosts.logfile_inc import LogfileIncScenario, MaxMorgan
from sshmitm.tutorial.hosts.logfile_inc.web01 import Web01
class PasswordAuthTutorial(Tutorial):
id = "01-password-auth"
title = "Password Authentication"
category = "Authentication"
description = "Intercept SSH password authentication via SSH-MITM."
scenario = LogfileIncScenario
host_aliases = {"proxy_target": Web01}
requires = [RandomPassword(MaxMorgan, Web01)]
steps = [
Step("intro", "What you will learn", condition=TRUE()),
Step("start-sshmitm", "Start SSH-MITM",
condition=PortOpen("sshmitm_port"),
command="ssh-mitm server --remote-host {proxy_target_address}"
" --remote-port {proxy_target_port}"
" --listen-port {sshmitm_port}"),
Step("intercept", "Enter the intercepted password",
condition=UserInput("web01_mmorgan_password",
prompt="Enter the password from the terminal:")),
]
Session data keys
ScenarioSession.template_vars() exposes all values for step command
and hint interpolation. The following keys are always available:
Key pattern |
Value |
|---|---|
|
Port on which the SSH-MITM proxy listens. |
|
IP address of the host (e.g. |
|
DNS name of the host. |
|
Port of the first SSH-like service. |
|
Port for a specific protocol (e.g. |
|
Generated or static password (from |
|
SHA-256 fingerprint of a generated key pair. |
any |
Passed through verbatim. |
Event types
Mock hosts emit events into ScenarioSession.events (an
asyncio.Queue). Condition classes can subscribe to this queue to
detect when a specific action has occurred.
Event class |
When emitted |
|---|---|
|
A user attempts authentication (success or failure). |
|
An SFTP upload or download completes. |
|
A non-interactive SSH exec command is run. |
|
An SSH session is opened or closed. |
|
A host-key fingerprint check is observed (CVE-2020-14145). |
All event classes live in sshmitm.tutorial._events.
Existing scenario: Logfile Inc.
All current tutorial chapters belong to LogfileIncScenario. The
declared hosts are:
Class |
Module |
Role |
|---|---|---|
|
|
Django application server, SSH + HTTP. Accepts password (mmorgan) and public-key (sking, lchen) auth. |
|
|
SFTP-only file server. Holds deployment artefacts and company docs. |
|
|
Network router CLI (SSH, SNMP). Shell outputs include the running configuration with the SNMP read-write community string. |
|
|
Self-hosted Git platform. Exposes |
|
|
PostgreSQL database. No SSH service — probed only via the user-validity oracle (CVE-2016-20012). |
For topology, addresses, and story details see Logfile Inc. — Scenario Guide.