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/PrivateTmpor 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.