Lab 01 — Cross-Platform Service Hardening Evaluator

Difficulty: 3/5 | Runs locally: yes

Pairs with the Phase 05 WARMUP Chapters 1–4, 9 (the four OS models, Linux confinement, Apple hardening) and the HITCHHIKERS-GUIDE ("The Service Hardening Method").

Why This Lab Exists (Purpose & Goal)

A network service is the unit of compromise on a server: when an attacker gets code execution, what they can do next is determined entirely by how that service was configured — what user it runs as, which capabilities/privileges it holds, what it can write, where it can connect, and whether anyone is watching. The goal of this lab is to build the evaluator that scores a service's hardening posture across Linux, Windows, and macOS from one normalized profile — turning "is this service hardened?" into a repeatable, idempotent assessment instead of a per-host guess.

The Concept, In the Weeds

The same six dimensions govern a service's blast radius on every OS; only the mechanism differs:

  • Privilege — does it run as root/SYSTEM when it doesn't need to? (Linux capabilities, Windows token privileges, macOS entitlements.)
  • Filesystem — is its executable or config writable by its own (or a lesser) identity? A writable binary on a privileged service is self-replace → escalation.
  • Network — unrestricted outbound is an exfiltration and SSRF path; restrict egress.
  • Secrets — secrets in environment variables leak into logs, crash dumps, and child processes.
  • Logging — absent audit logs mean a compromise is invisible (you can't respond to what you don't record).
  • Recovery — no resource limits (DoS) and untested rollback (you can't safely undo a bad change).

The design lesson is in the architecture: a normalized profile lets platform-specific collectors (systemd / Windows service config / launchd) feed one tested evaluator. And the operating discipline the evaluator encodes — separate recommendation from evidence from confidence, and harden without silently breaking the service — is what distinguishes a useful tool from a CIS-benchmark script that locks a host into an outage.

Why This Matters for Protecting the Company

Most servers run dozens of services, each a potential foothold, and the default configuration is rarely least-privilege. A hardening evaluator lets a security team assess and improve the posture of a whole fleet consistently — dropping unneeded privileges, locking down writable paths, restricting egress, ensuring telemetry exists — which directly shrinks the blast radius of the next unknown service vulnerability. The "harden without breaking" rule is what makes the program adoptable: hardening that causes outages gets reverted, leaving you worse off than before.

Build It

Implement the evaluator over normalized service profiles: identify privilege, filesystem, network, logging, secret, and recovery gaps. Output should distinguish recommendation, evidence, and confidence, and be idempotent.

LAB_MODULE=solution pytest -q

Attack / Failure Cases

Root/admin execution; writable executable paths; unrestricted outbound networking; secrets in environment variables; absent audit logs; no resource limits; untested rollback.

Secure Implementation Patterns

The evaluator — the six blast-radius dimensions, default-deny (mirrors solution.py):

def evaluate(p: ServiceProfile) -> tuple[str, ...]:
    findings = []
    if p.privileged:                          findings.append("EXCESSIVE_PRIVILEGE")
    if p.executable_writable_by_service:      findings.append("WRITABLE_EXECUTABLE_PATH")  # self-replace -> persistence
    if not p.read_only_root:                  findings.append("WRITABLE_ROOT_FILESYSTEM")
    if "*" in p.outbound_destinations or not p.outbound_destinations:
        findings.append("UNRESTRICTED_OR_UNDEFINED_EGRESS")                                # exfil/SSRF path
    if p.secrets_in_environment:              findings.append("SECRET_IN_ENVIRONMENT")      # leaks to logs/children
    if not p.audit_logging:                   findings.append("MISSING_AUDIT_LOGGING")      # blind to compromise
    if not p.memory_limit_mb:                 findings.append("MISSING_MEMORY_LIMIT")
    if not p.process_limit:                   findings.append("MISSING_PROCESS_LIMIT")      # fork-bomb / DoS
    if not p.rollback_tested:                 findings.append("UNTESTED_ROLLBACK")
    return tuple(sorted(findings))

The hardened systemd unit each finding maps to:

[Service]
User=svc-app                      # not root  -> EXCESSIVE_PRIVILEGE
NoNewPrivileges=yes
ProtectSystem=strict              # read-only root -> WRITABLE_ROOT_FILESYSTEM
CapabilityBoundingSet=CAP_NET_BIND_SERVICE   # drop the rest
SystemCallFilter=@system-service  # seccomp allowlist
RestrictAddressFamilies=AF_INET AF_INET6     # bounded egress
MemoryMax=256M
TasksMax=64                       # MISSING_PROCESS_LIMIT
LoadCredential=token:/run/secrets/token      # NOT Environment= (SECRET_IN_ENVIRONMENT)

Production practices to carry forward:

  • Inventory the real needs first, then drop everything else — harden incrementally with functional + denial tests, never a blind CIS apply that breaks the service.
  • A writable service binary on a privileged service is self-replace → escalation — lock the ACL.
  • Secrets via files/secret-stores, never environment variables (they leak into logs, crash dumps, and child processes).
  • Verify effective state, not the unit file (systemd-analyze security, systemctl show); and keep a tested rollback so a bad change is reversible.

Validation — What You Should Be Able to Do Now

  • Map the six hardening dimensions to each OS's mechanism (Linux capabilities/seccomp/systemd, Windows token/ACLs, macOS entitlements).
  • Harden a service without breaking it — inventory real needs first, change incrementally, test startup/error/DNS/upgrade paths, keep rollback.
  • Explain why a writable service binary, secrets-in-environment, or missing telemetry each expands the blast radius.

The Broader Perspective

This lab trains the instinct of least privilege as a measurable, fleet-wide property — not a one-off heroic config but a posture you can score, enforce, and regress. That instinct scales straight up to cloud workload identity and IAM (Phase 07) and to sandbox authority enumeration (Phase 06): in every case you ask "what does this thing actually need, and what is it granted?" and you close the gap. The cross-platform normalization also teaches a tooling pattern you'll reuse constantly — collect into one schema, evaluate with one policy — which is the only way security tooling scales across a heterogeneous estate.

Interview Angle

  • "Design isolation for a Linux network service." — Dedicated non-root user; drop all capabilities except those needed; NoNewPrivileges; seccomp allowlist of its real syscalls; ProtectSystem/PrivateTmp or an AppArmor/SELinux profile; restrict address families. Inventory needs first, then minimal policy, then prove denied ops fail without breaking the service.

Extension (Stretch)

Write the platform collectors (systemd / Windows service / launchd) that populate the profile, and run the evaluator across an owned fleet with rollback tests.

References

  • Phase 05 WARMUP Chapters 1–4, 9; CIS Benchmarks (apply with workload context).
  • systemd hardening directives; Sysinternals; Apple launchd/Endpoint Security documentation.