Skip to content

Browser-delivered attacker workstations (Pwnbox/AttackBox-style) #268

Description

@tbcsec

Filed as a single feature / tracking issue (owner decision — not an epic with
child issues). The phased breakdown below is tracked as sub-tasks within this issue.

Depends on #266 (on-demand per-team challenge instances): a workstation is
effectively another instance kind on the same provisioner/routing/lifecycle/
hardening substrate. The A/D-integration sub-task additionally depends on #267.

Positioning: removes the "install your own tooling" barrier — the HTB Pwnbox /
THM AttackBox pattern, which no self-hosted jeopardy platform bundles.

Summary

Competitors currently need their own configured attack VM (Kali/Parrot) to play. This
adds a per-user (or per-team) browser-accessible workstation: a web terminal and/or
full GUI desktop running a curated tooling image, launched from the challenge UI and
reachable over the existing reverse proxy, authenticated to the platform session.

A workstation is modelled as a new instance kind on the #266 provisioner, with its
own lifecycle (one active per user, longer TTL, extend/reset) and a stricter network
policy.

Motivation & competitive context

Requiring local tooling is a real barrier for education, beginners, on-site events, and
locked-down corporate/exam environments. HackTheBox Pwnbox and TryHackMe AttackBox
prove the demand and the retention value. No self-hosted CTF platform (CTFd, rCTF) ships
an in-browser workstation. Delivered on top of the instancing feature, this is a
distinctive, education-and-enterprise-friendly differentiator.

Goals

  1. A workstation instance kind on the On-demand, per-team containerised challenge instances #266 provisioner (Docker MVP, Kubernetes for
    scale), running a curated tooling image.
  2. Browser access: a web terminal (PTY over WebSocket) and, later, a GUI
    desktop
    (noVNC), proxied through Caddy and bound to the user's authenticated session
    via short-lived signed tokens (mirroring signed-attachment URLs).
  3. Lifecycle: one active workstation per user; TTL + idle reaping; extend and
    reset (wipe & recreate); auto-reap at competition end.
  4. Network policy: the workstation may reach permitted challenge instances (and, in
    A/D, opponents' services) but not the platform control plane, other users'
    workstations, or the internet — with a per-competition egress toggle.
  5. Guardrails: resource limits, per-user quota, session lifetime, launch audit, abuse
    handling.
  6. Delivered as an opt-in module, off by default, per-competition toggle.

Non-goals (initial)

  • Persistent per-user home directories across events (optional later; needs storage).
  • Arbitrary user-supplied images (curated organiser images only in v1).
  • Windows workstations.

Proposed design

Workstation as an instance kind

Reuse the Provisioner abstraction and lifecycle from #266; add a workstation kind
with its own spec (image, resources, GUI vs terminal, egress policy) and lifecycle policy
(one-active-per-user, longer TTL, reset). Propose a short ADR only for the access/auth
model and network policy, if not already covered by the instancing ADR.

Access layer

  • Terminal: a PTY-over-WebSocket broker (ttyd/wetty-style) proxied through Caddy; the
    WS is authorised by a short-lived signed token scoped to the instance and user.
  • GUI (later): noVNC → a VNC server in the container (lightweight XFCE), or Apache
    Guacamole (guacd) if RDP/VNC/SSH multiplexing is wanted. Document the trade-off
    (noVNC simpler; Guacamole heavier but more capable).

Session binding & presence

Only the owning user may attach; token scoped to the instance. Integrate with the
presence system ("in workstation"). Push status over WebSockets like other instances.

Networking / isolation

  • Default deny egress; per-competition opt-in (some challenges need internet).
  • Allowlist to the challenge-instance network only; deny platform-internal
    services (Postgres, Redis, MinIO, API control plane) and peer workstations.
  • On cloud, block the metadata IP 169.254.169.254; apply seccomp/AppArmor, dropped
    capabilities, no-new-privileges, non-root where feasible.
  • In A/D, the allowlist extends to opponents' service endpoints via the endpoint registry
    (First-class Attack-Defense & King-of-the-Hill competition modes #267).

Lifecycle

One active workstation per user; TTL + idle detection (reap idle sessions); extend and
reset; auto-reap at competition end. Enforce a global concurrency ceiling
(workstations are heavier than challenge instances).

Frontend

  • A "Launch workstation" control (global for the competition, not per challenge).
  • Embedded terminal/desktop panel (component or iframe) with status, countdown,
    extend, reset.
  • Clear messaging when at capacity or when egress is disabled.

RBAC & config

  • Permission workstation_launch.
  • Per-competition module toggle (off by default).
  • Global .env: workstation image, resource limits, GUI-vs-terminal, egress policy,
    concurrency cap, idle timeout.

Security (critical — these are shells with network reach)

Beyond the shared #266 hardening: strict egress control (prevent using workstations to
attack third parties or mine crypto), launch auditing and rate limits, cloud-metadata
blocking, and a clear acceptable-use note surfaced at launch. Treat capacity/cost as a
first-class concern.

Scaling

Workstations are the heaviest instance kind. Provide sizing guidance and concurrency
caps; the Kubernetes backend (and the existing Fargate/ALB deployment path, ADR-0031) is
the natural home for large events.

Phasing

  • Phase 1 — Terminal MVP: Docker backend; terminal-only; one-active-per-user; TTL
    reaping; session-bound WS proxy; egress off; curated image; launch UI.
  • Phase 2 — GUI + UX: noVNC desktop option; extend/reset; presence integration;
    lifecycle events + automation.
  • Phase 3 — Scale & isolation: Kubernetes backend + NetworkPolicy; configurable
    egress; A/D integration (reach opponents); metadata/cloud-cred hardening.
  • Phase 4 — Optional: persistent per-user storage; multiple images / image picker.

Proposed breakdown (sub-tasks tracked within this issue)

  • ADR: workstation access/auth model + network policy (signed session-token, WS proxy auth, default network policy) — if not covered by the On-demand, per-team containerised challenge instances #266 ADR
  • workstation instance kind + spec + one-active-per-user lifecycle (longer TTL; reset semantics) — depends on On-demand, per-team containerised challenge instances #266
  • Curated tooling image (Kali/Parrot-lite) + reproducible build pipeline (non-root)
  • Terminal broker (PTY over WebSocket) + Caddy proxy + signed session token (scoped to user+instance)
  • Competitor UI: launch, embedded terminal, timer, extend, reset (capacity/egress messaging)
  • Network isolation: deny control plane + peers; default-deny egress; allowlist to challenge-instance network
  • RBAC (workstation_launch) + per-competition module toggle + global config
  • Idle detection + TTL reaping + global concurrency ceiling ("at capacity" behaviour)
  • GUI desktop option (noVNC) + presence integration ("in workstation")
  • Lifecycle events (workstation.started/.expired/.reset) + automation actions
  • Kubernetes backend + NetworkPolicy + configurable egress (metadata IP blocked on cloud)
  • A/D integration: allowlist opponents via endpoint registry — depends on First-class Attack-Defense & King-of-the-Hill competition modes #267
  • Security hardening + acceptable-use surfacing + launch audit (seccomp/AppArmor/caps, rate limits)
  • Docs: enabling workstations + sizing/capacity guidance
  • (Optional, Phase 4) Persistent per-user storage + image picker

Overall acceptance criteria

  • A competitor can launch a browser terminal (later, desktop) from the competition UI,
    authenticated to their session, and use it against permitted challenge instances.
  • One active workstation per user; idle/TTL reaping and a global concurrency cap work.
  • The workstation cannot reach the platform control plane, peer workstations, or the
    internet unless egress is explicitly enabled.
  • The feature is off by default and toggled per competition.

Risks & open questions

  • Cost & capacity — the biggest risk; workstations are heavy. Concurrency caps and
    sizing guidance are mandatory.
  • Security of handing users shells with network reach — isolation and egress control
    are non-negotiable; cloud-metadata exposure must be blocked.
  • Egress abuse (attacks on third parties, mining) — default-deny + AUP + audit.
  • Per-user vs per-team workstations — decide early.
  • Persistence — whether/when to offer durable home directories.

Derived from an internal feature brief. This is the canonical tracking issue for the feature.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions