Absichtlich verwundbares Docker-Lab mit einem routbaren DNS-Bestand und maschinenlesbaren Antwortschlüsseln pro Ziel, das die Präzision, Trefferquote und den Umfang von Scannern lokal bewertet.
Ein Sicherheitstest-Labor. Eine Flotte absichtlich verwundbarer Ziele, ein routingfähiges Netzwerk-Ensemble mit autoritativem DNS zur Asset-Erkennung zum Aufzählen, ein maschinenlesbarer Lösungsschlüssel pro Ziel und ein Bedienfeld, um alles zu steuern.
Alles hier ist absichtlich verwundbar. Nur lokales Testen. Nicht ins Internet oder ein nicht vertrauenswürdiges Netzwerk exponieren. Veröffentlichte Ports binden an
127.0.0.1; Laborziele binden an gar nichts.Unser eigener Scanner erhält absichtlich RCE innerhalb dieser Container, daher wird der Container als Sicherheitsgrenze behandelt: jedes Image ist per Digest gepinnt, jeder Dienst verwirft alle Capabilities und fügt ein Minimum wieder hinzu, und
./lime auditerzwingt dies. Lies SECURITY.md vor dem ersten Start, einschließlich des Teils darüber, was Container-Isolation nicht abdeckt.
Das Bedienfeld auf http://127.0.0.1:7000, das das Labor im laufenden Betrieb zeigt.
limeyard war vuln_apps. Es wurde umbenannt, weil es aufgehört hat, ein Ordner von
Anwendungen zu sein: Es enthält jetzt nackte Dienste, eine DNS-Zone, ein WAF-Paar, ein Präzisionsziel
und APK-Fixtures, von denen keines Apps sind.
Die alte Flotte erzielte 9 von 9, null Fehlschläge, null Falsch-Positive. Ein Benchmark, der nicht fehlschlagen kann, kann keine Regression erkennen. Drei Dinge waren strukturell falsch:
127.0.0.1:70xx,
also hatte Subdomain-Enumeration nichts aufzuzählen, Port-Scanning bekam
seine Antwort geliefert, und Service-Fingerprinting sah nie einen Nicht-HTTP-Daemon.Du brauchst Docker (mit dem Compose-Plugin) und git. Sonst nichts.
curl -fsSL https://raw.githubusercontent.com/clickswave/limeyard/main/install.sh | bash
Das klont das Labor nach ./limeyard, schreibt seine .env mit einem frischen API-
Token, baut und startet die Control Plane und fragt dann, was ausgeführt werden soll. Bevor
irgendetwas startet, zeigt es, was die Auswahl kostet, gemessen im Leerlauf auf der
Referenzbox, gegen das, was deine Maschine frei hat:
This selection, idle, on the box it was measured on:
17 targets, 1 scenarios, 45 containers
RAM about 2.9 GB resident (host has 22.4 GB available)
disk about 11.0 GB of images to pull (host has 111 GB free)
CPU near idle once up (3% of one core); pulling and first boots are the busy part
Start it? [y/N]
Nicht-interaktiv: curl ... | bash -s -- --light --yes (oder --all,
--none, --pick dvwa,juice-shop,estate). Lege den Checkout woanders ab mit
LIMEYARD_DIR=/path.
Das Panel ist dann unter http://127.0.0.1:7000, und dieselben Dinge von Hand:
./lime setup # der Assistent erneut, jederzeit
./lime start --all # jedes leichte Ziel
./lime start crapi --heavy # ein schweres, explizit
./lime scenario-up estate # das Netzwerk-Ensemble: DNS, vhosts, Dienste
./lime status # was läuft
./lime stop --all --heavy # alles herunterfahren; Images und Volumes bleiben
./lime credits # wer jedes Ziel geschrieben hat, und unter welcher Lizenz
./lime doctor # Umgebungs-, Attributions- und Festplattenprüfungen
./lime doctor --fix # jede automatische Abhilfe der Prüfungen anwenden, dann erneut prüfen
./lime audit # Container-Hardening + Supply-Chain-Invarianten
./lime pin # Image-Drift gegen die Registry melden
Von Hand, ohne den Installer:
git clone https://github.com/clickswave/limeyard && cd limeyard
cp .env.example .env
echo "LIMEYARD_DIR=$PWD" >> .env
echo "LIME_TOKEN=$(openssl rand -hex 24)" >> .env # erforderlich, siehe SECURITY.md
docker compose up -d --build # Control Plane + UI auf http://127.0.0.1:7000
./lime setup
Jedes Manifest trägt einen gemessenen resources-Block (Container, Leerlauf-RAM,
Image-Festplatte, Leerlauf-CPU). Der Assistent, der Auswahlstreifen des Panels und die
Seite jedes Ziels summieren daraus, sodass die Schätzung überall dieselbe ist.
Zwei, und nur zwei.
main ist das, was der Installer klont und was du bekommst, wenn du nichts tust.
Es bewegt sich per Pull Request, niemals per direktem Push.dev ist der Standard-Branch und wo Arbeit landet. Öffne Pull Requests
dagegen.| target | eine Sache unter Test, von einer deklarierten kind. Besitzt eine Compose-Datei, optionales Setup und ihren eigenen Lösungsschlüssel. Läuft als eigenes isoliertes Compose-Projekt, sodass zwei Ziele, die Postgres verwenden, nie eines teilen |
| scenario | mehrere Ziele, die in eine Netzwerktopologie mit autoritativem DNS verdrahtet sind. Woran Asset-Erkennung gemessen wird |
| truth | der maschinenlesbare Lösungsschlüssel. Siehe truth/schema.md |
| doctor | eine Liste von Prüfungen mit jeweils einem Urteil: Umgebung, Attribution, Supply Chain, Hardening. Prüfungen mit einer eindeutigen Abhilfe tragen einen Ein-Klick-Fix im Panel (/doctor) und --fix auf der CLI: Netzwerke erstellen, Festplatte zurückgewinnen, Images pinnen, Quellen abrufen, laufende Ziele erneut verifizieren, Off-Loopback-Binds umschreiben. Attribution, Portkonflikte und Hardening brauchen einen Menschen |
Arten: web api bench cve service estate edge control mobile.
targets/<kind>/<slug>/ target.yml, compose.yml, setup.sh, truth.yml
scenarios/<slug>/ scenario.yml, compose.yml, zones/
control/limed/ der Daemon: CLI + HTTP API + Scorer
control/ui/ das SvelteKit-Bedienfeld
truth/ der Vertrag, und datierte Scorecards
docker compose up -d startet zwei Container und sonst nichts: limeyard_control
(der limed-Daemon, der den Docker-Socket hält) und limeyard_ui (das SvelteKit-
Panel). Beide binden nur an Loopback. Das Panel ist unter http://127.0.0.1:7000 und die
rohe API unter http://127.0.0.1:7099. Beide brauchen .env, und LIME_TOKEN darin ist
obligatorisch: Das Panel hält das Token serverseitig und der Browser sieht es nie.
| Seite | wofür sie ist |
|---|---|
| Targets | jedes Ziel, filtern nach Art und Zustand, sortieren, Mehrfachauswahl mit Start, Stop, Restart. Klicke eine Zeile für das Ziel |
| Target | Fakten (Adresse, Anmeldedaten, Stack, Upstream, Verifikation, Image-Digests), das Live-Log und der Lösungsschlüssel mit seinen Negativen |
| Scenarios | das Ensemble: Resolver, Zonen, Subnetz und eine Host-Tabelle mit Container-Zustand pro Container. Hochfahren, herunterfahren, neu starten |
| Scorecard | der letzte Lauf mit Deltas, Abdeckung pro Klasse und pro Ziel, verpasste IDs, eine Historie, die du ansehen kannst, und ein Zwei-Läufe-Diff |
| Ports | was auf localhost bindet, und was nur auf der Labor-Bridge existiert |
| Doctor | eine Liste von Prüfungen mit jeweils einem Urteil. Fix und Fix all zeigen zuerst die exakten Befehle und Dateibearbeitungen, berechnet aus dem Labor wie es ist, und führen sie auf Bestätigung aus. Ein Fix, der seine Prüfung fehlschlagen lässt, sagt das |
| Credits | wer jedes Ziel geschrieben hat und unter welcher Lizenz |
Das Panel aktualisiert sich selbst: limed streamt Zustandsübergänge über SSE, sodass ein von der CLI gestartetes Ziel ohne Aktualisierung erscheint. Der Header trägt die CPU, den RAM und die freie Festplatte des Hosts aus demselben Stream, eingefärbt nur, wenn sie es wert sind, bemerkt zu werden. Jede Tabelle sortiert durch Klicken auf einen Spaltenkopf; ein zweiter Klick kehrt die Richtung um.
Nach einer Änderung am Panel oder am Daemon, baue das Paar neu:
docker compose up -d --build
Um am Panel gegen einen laufenden Daemon zu arbeiten, ohne das Image neu zu bauen:
cd control/ui && npm install
LIMED_URL=http://127.0.0.1:7099 LIME_TOKEN=<from .env> npm run dev # :7000
Jeder Aufruf außer /api/health braucht X-Lime-Token.
GET /api/targets list, with state and attribution
GET /api/targets/<slug> plus truth, images, lab addresses
GET /api/targets/<slug>/logs SSE, docker compose logs -f
POST /api/targets/<slug>/<action> start | stop | restart | pull | setup
POST /api/targets/bulk {action, slugs}: a pool of three, per-slug refusals
GET /api/scenarios with per-host container state
POST /api/scenarios/<slug>/<action> up | down | restart
GET /api/scorecards newest first, by_class carries false positives
GET /api/scorecards/<id> one card
POST /api/score {tool, findings, save?, targets?}
GET /api/doctor checks with verdict, reason, value, items, fix
POST /api/doctor/fix {ids}: those checks, or every fixable one when empty
GET /api/ports /api/credits /api/truth /api/status
GET /api/events SSE: state, scenario, tick
Drei Tiers, weil eines das ursprüngliche Problem war.
lime-web Bridge. Web- und API-Ziele, veröffentlicht auf 127.0.0.1:70xx.lime-lab Bridge, 10.66.0.0/16, statische IPs, keine Host-Bindung. Ein Scanner
tritt diesem Netzwerk als Container bei und sieht ein echtes Subnetz mit echten Hosts und
echten Ports statt einer Loopback-Portliste.lime-edge der WAF-Tier, mit erreichbarem Origin, aber abwesend aus DNS.DNS ist autoritatives BIND auf .test-Zonen (RFC 6761 reserviert es). Docker-Netzwerk-
Aliase sind absichtlich nicht die Quelle der Wahrheit: Sie erscheinen nie in einer Zonen-
Übertragung und würden die Topologie mit dem Lösungsschlüssel in Widerspruch bringen.
| Bereich | Verwendung |
|---|---|
| 7000 | die UI |
| 7099 | limed API |
| 7001-7099 | Web- und API-Ziele |
| 7100-7199 | Benchmark-Suiten |
| 7200-7299 | CVE-Labs |
| 7300-7399 | Edge- und Control-Ziele |
| 5353 | Labor-DNS |
| keine | Service- und Estate-Ziele, nur lime-lab-Adressen |
Jedes Ziel liefert eine truth.yml. Der Scorer verwandelt Findings in Präzision,
Recall und F1, pro Ziel und pro Klasse:
curl -s -XPOST localhost:7099/api/score -H 'Content-Type: application/json' \
-d '{"tool":"crossfyre","save":true,"findings":[...]}'
Drei Dinge werden gezählt, nicht eines. Recall: Haben wir gefunden, was da ist.
Präzision: Haben wir vermieden zu melden, was nicht da ist, gemessen an den
negative-Einträgen, die jeder Lösungsschlüssel trägt. Scope: Was wir korrekt nicht
versucht haben, festgehalten, damit es niemand jedes Quartal neu aufrollt.
Das mirage-Ziel existiert nur für das zweite. Nichts darin ist verwundbar
und alles darin sieht so aus, als wäre es das, also ist jedes Finding dagegen ein Falsch-
Positiv durch Konstruktion.
CONTRIBUTING.md enthält das Ganze: was ein Beitrag
üblicherweise ist, die Invarianten, die ./lime audit erzwingt, und warum eine Korrektur eines
Lösungsschlüssels hier mehr wert ist als ein neues Feature. Die Kurzfassung der
Regeln steht unten. Alle Teilnehmenden sind an den
Verhaltenskodex gebunden.
targets/<kind>/<slug>/
target.yml manifest, including a REQUIRED upstream block
compose.yml the containers. 127.0.0.1 binds only
setup.sh optional one-time init, run after start
truth.yml the answer key
Regeln, die das Labor sauber halten:
127.0.0.1.[lime-web] bei. Füge kein projektbezogenes
Netzwerk hinzu: zu viele Netzwerke und Docker geht der Adresspool aus.[default, lime-web] bei und platzieren die Datenbank nur auf
[default]. Jedes Ziel bekommt seinen eigenen Datenbankdienst und sein eigenes Volume.[lime-lab] mit
einer statischen IP bei und veröffentlichen keinen Host-Port.upstream ist obligatorisch. lime doctor lässt ein Ziel ohne es durchfallen und der
Manager weigert sich, eines zu registrieren. Siehe unten.repo: und compose: ins
Manifest und die Quelle wird zur Laufzeit nach <target>/src abgerufen../lime measure <slug>
gibt den resources-Block zum Einfügen aus. Der Installer und das Panel addieren
diese, um jemanden zu warnen, bevor er es startet, also ist eine Schätzung hier eine Lüge dort.Ziele sind absichtlich verwundbare Software anderer Leute, also wird die Herkunft festgehalten statt angenommen, und die Laufzeit wird eingeschränkt statt vertraut. SECURITY.md enthält das vollständige Bild; die Kurzfassung:
./lime pin meldet Drift.raesene/bwapp (archiviert, zuletzt neu gebaut
2022, keine Lizenz) und delfer/alpine-ftp-server (eine Einzelperson), werden
als solche benannt.no-new-privileges, cap_drop: ALL plus einem minimalen
cap_add pro Image, einer PID-Obergrenze und einer Speicherobergrenze. Datenbank-Tiers sitzen auf
internal: true-Netzwerken ohne Route nach draußen.limed hält den Docker-Socket, was root auf dem Host ist, also verlangt es ein
gemeinsames Geheimnis bei jedem Aufruf. Ein gekapertes Ziel kann es erreichen und nichts erfahren../lime audit schlägt fehl bei privileged, Host-Netzwerk, einem Socket-Mount in einem
Ziel, einem Off-Loopback-Bind, einem ungepinnten Image oder einem fehlenden Token.Nichts hier verteidigt gegen eine Container-Escape auf Kernel-Ebene. Dafür nutze eine Wegwerf-VM.
Fast alles hier wurde von jemand anderem geschrieben, und mehrere Ziele deklarieren überhaupt keine Lizenz. Also ist das Nennen des Autors ein hartes Gate, keine Konvention:
target.yml trägt einen erforderlichen upstream-Block: Autor, Repo, Lizenz
und das Datum, an dem wir zuletzt verifiziert haben, dass es baut. packager hält die Person fest, die
etwas containerisiert hat, wenn das von der Person abweicht, die es geschrieben hat.none declared wird als
Warnung dargestellt, was auch das Nicht-Weiterverbreiten-Signal ist./credits in der UI und ./lime credits listen jedes Ziel, jeden Autor und jede
Lizenz auf. ./lime credits --markdown regeneriert den Abschnitt unten.limeyard führt die Arbeit anderer aus. Jedes Ziel und Szenario unten wurde von jemand anderem gebaut, sofern es nicht Clickswave sagt.
| Ziel | Autor | Lizenz | Quelle |
|---|---|---|---|
| mirage | Clickswave | MIT | repo |
| Ziel | Autor | Lizenz | Quelle |
|---|---|---|---|
| Log4Shell lab | Christophe Tafani-Dereeper (christophetd) | Apache-2.0 | repo |
| Ziel | Autor | Lizenz | Quelle |
|---|---|---|---|
| ModSecurity CRS pair | OWASP Core Rule Set project (coreruleset) | Apache-2.0 | repo |
| Ziel | Autor | Lizenz | Quelle |
|---|---|---|---|
| AndroGoat | Satish Patnayak | none declared | repo |
| Szenario | Autor | Lizenz | Quelle |
|---|---|---|---|
| estate | Clickswave | MIT | - |
| Ziel | Autor | Lizenz | Quelle |
|---|---|---|---|
| Open services | Clickswave (composition of upstream official images) | mixed, per-image | - |
| Ziel | Autor | Lizenz | Quelle |
|---|---|---|---|
| bWAPP | Malik Mesellem (pkg: Rory McCune (raesene)) | none declared | repo |
| DVWA | Robin Wood (digininja) | GPL-3.0 | repo |
| FaultLine ISP | Clickswave | MIT | repo |
| OWASP Juice Shop | Bjoern Kimminich (OWASP Juice Shop project) | MIT | repo |
| OWASP Mutillidae II | Jeremy Druin (webpwnized), OWASP Mutillidae II | GPL-3.0 | repo |
| OWASP RailsGoat | OWASP RailsGoat project | MIT | repo |
| OWASP WebGoat + WebWolf | OWASP WebGoat project | GPL-2.0 | repo |