Laboratorio Docker deliberatamente vulnerabile con un'infrastruttura DNS instradabile e chiavi di risposta leggibili da macchina per ogni target, valutando localmente precisione, richiamo e ambito dello scanner.
Un laboratorio di security testing. Una flotta di target deliberatamente vulnerabili, un'infrastruttura di rete instradabile con DNS autoritativo per l'asset discovery da enumerare, una answer key leggibile dalla macchina per ogni target e un pannello di controllo per gestire tutto.
Tutto qui è deliberatamente vulnerabile. Solo test locale. Non esporlo a Internet o a una rete non attendibile. Le porte pubblicate si legano a
127.0.0.1; i target del lab non si legano a nulla.Il nostro scanner ottiene RCE all'interno di questi container di proposito, quindi il container è trattato come un confine di sicurezza: ogni immagine è fissata tramite digest, ogni servizio rimuove tutte le capability e ne riaggiunge un minimo, e
./lime auditlo impone. Leggi SECURITY.md prima del primo avvio, inclusa la parte su cosa l'isolamento dei container non copre.
Il pannello di controllo su http://127.0.0.1:7000, che mostra il lab in esecuzione.
limeyard era vuln_apps. È stato rinominato perché ha smesso di essere una cartella di applicazioni: ora contiene servizi nudi, una zona DNS, una coppia di WAF, un target di precisione e fixture APK, nessuno dei quali è un'app.
La vecchia flotta totalizzava 9 su 9, zero mancati, zero falsi positivi. Un benchmark che non può fallire non può rilevare una regressione. Tre cose erano strutturalmente sbagliate:
127.0.0.1:70xx, quindi l'enumerazione dei sottodomini non aveva nulla da enumerare, il port scanning riceveva la sua risposta e il service fingerprinting non vedeva mai un demone non-HTTP.Ti servono Docker (con il plugin compose) e git. Nient'altro.
curl -fsSL https://raw.githubusercontent.com/clickswave/limeyard/main/install.sh | bash
Questo clona il lab in ./limeyard, scrive il suo .env con un nuovo API token, compila e avvia il control plane, poi chiede cosa eseguire. Prima che qualsiasi cosa parta mostra quanto costa la selezione, misurato a riposo sulla macchina di riferimento, rispetto a quanto la tua macchina ha libero:
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]
Non interattivo: curl ... | bash -s -- --light --yes (oppure --all,
--none, --pick dvwa,juice-shop,estate). Metti il checkout altrove con
LIMEYARD_DIR=/path.
Il pannello è poi su http://127.0.0.1:7000, e le stesse cose a mano:
./lime setup # the wizard again, any time
./lime start --all # every light target
./lime start crapi --heavy # a heavy one, explicitly
./lime scenario-up estate # the network estate: DNS, vhosts, services
./lime status # what is up
./lime stop --all --heavy # everything down; images and volumes stay
./lime credits # who wrote each target, and under what licence
./lime doctor # environment, attribution and disk checks
./lime doctor --fix # apply every check's automatic remedy, then re-check
./lime audit # container hardening + supply chain invariants
./lime pin # report image drift against the registry
A mano, senza l'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 # required, see SECURITY.md
docker compose up -d --build # control plane + UI on http://127.0.0.1:7000
./lime setup
Ogni manifest porta un blocco resources misurato (container, RAM a riposo, disco delle immagini, CPU a riposo). Il wizard, la striscia di selezione del pannello e la pagina di ogni target sommano da lì, quindi la stima è la stessa ovunque.
Due, e solo due.
main è ciò che l'installer clona e ciò che ottieni se non fai nulla.
Si muove tramite pull request, mai tramite push diretto.dev è il branch predefinito ed è dove atterra il lavoro. Apri le pull request
contro di esso.| target | una cosa sotto test, di un kind dichiarato. Possiede un file compose, un setup opzionale e la propria answer key. Viene eseguito come proprio progetto compose isolato, quindi due target che usano Postgres non ne condividono mai uno |
| scenario | diversi target collegati in una topologia di rete con DNS autoritativo. È ciò contro cui viene valutata l'asset discovery |
| truth | la answer key leggibile dalla macchina. Vedi truth/schema.md |
| doctor | un unico elenco di controlli con un verdetto ciascuno: ambiente, attribuzione, supply chain, hardening. I controlli con un rimedio inequivocabile portano un fix con un clic nel pannello (/doctor) e --fix sulla CLI: crea reti, recupera disco, fissa immagini, scarica sorgenti, ri-verifica i target in esecuzione, riscrive i bind fuori dal loopback. Attribuzione, conflitti di porte e hardening richiedono una persona |
Tipi: 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/ the daemon: CLI + HTTP API + scorer
control/ui/ the SvelteKit control panel
truth/ the contract, and dated scorecards
docker compose up -d avvia due container e nient'altro: limeyard_control
(il demone limed, che detiene il socket Docker) e limeyard_ui (il pannello
SvelteKit). Entrambi si legano solo al loopback. Il pannello è su http://127.0.0.1:7000 e
l'API grezza su http://127.0.0.1:7099. Entrambi richiedono .env, e LIME_TOKEN al suo interno è
obbligatorio: il pannello detiene il token lato server e il browser non lo vede mai.
| pagina | a cosa serve |
|---|---|
| Targets | ogni target, filtra per tipo e stato, ordina, selezione multipla con Start, Stop, Restart. Clicca una riga per il target |
| Target | fatti (indirizzo, credenziali, stack, upstream, verifica, digest delle immagini), il log live e la answer key con i suoi negativi |
| Scenarios | l'estate: resolver, zone, subnet e una tabella degli host con lo stato per container. Avvia, ferma, riavvia |
| Scorecard | l'ultima esecuzione con i delta, copertura per classe e per target, id mancati, uno storico consultabile e un diff tra due esecuzioni |
| Ports | cosa si lega su localhost e cosa esiste solo sul bridge del lab |
| Doctor | un unico elenco di controlli con un verdetto ciascuno. Fix e Fix all mostrano prima i comandi esatti e le modifiche ai file, calcolati dal lab così com'è, e li eseguono alla conferma. Un fix che lascia il suo controllo fallito lo dice |
| Credits | chi ha scritto ogni target e sotto quale licenza |
Il pannello si aggiorna da solo: limed trasmette le transizioni di stato via SSE, quindi un target avviato dalla CLI compare senza refresh. L'intestazione porta la CPU, la RAM e il disco libero dell'host dallo stesso stream, colorati solo quando vale la pena notarli. Ogni tabella si ordina cliccando l'intestazione di una colonna; un secondo clic inverte la direzione.
Dopo una modifica al pannello o al demone, ricompila la coppia:
docker compose up -d --build
Per lavorare sul pannello contro un demone in esecuzione senza ricompilare l'immagine:
cd control/ui && npm install
LIMED_URL=http://127.0.0.1:7099 LIME_TOKEN=<from .env> npm run dev # :7000
Ogni chiamata tranne /api/health richiede 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
Tre livelli, perché uno solo era il problema originale.
lime-web bridge. Target web e API, pubblicati su 127.0.0.1:70xx.lime-lab bridge, 10.66.0.0/16, IP statici, nessun binding sull'host. Uno scanner
si unisce a questa rete come container e vede una vera subnet con veri host e
vere porte invece di un elenco di porte su loopback.lime-edge il livello WAF, con l'origin raggiungibile ma assente dal DNS.Il DNS è BIND autoritativo su zone .test (la RFC 6761 la riserva). Gli alias di rete
Docker deliberatamente non sono la fonte di verità: non compaiono mai in un zone
transfer e farebbero divergere la topologia dalla answer key.
| range | uso |
|---|---|
| 7000 | la UI |
| 7099 | limed API |
| 7001-7099 | target web e api |
| 7100-7199 | suite di benchmark |
| 7200-7299 | lab CVE |
| 7300-7399 | target edge e control |
| 5353 | DNS del lab |
| nessuna | target service ed estate, solo indirizzi lime-lab |
Ogni target distribuisce un truth.yml. Lo scorer trasforma i finding in precision,
recall e F1, per target e per classe:
curl -s -XPOST localhost:7099/api/score -H 'Content-Type: application/json' \
-d '{"tool":"crossfyre","save":true,"findings":[...]}'
Si contano tre cose, non una. Recall: abbiamo trovato ciò che c'è.
Precision: abbiamo evitato di segnalare ciò che non c'è, misurato rispetto alle
voci negative che ogni answer key porta. Scope: ciò che correttamente non abbiamo
tentato, registrato così nessuno lo rimette in discussione ogni trimestre.
Il target mirage esiste solo per il secondo. Nulla in esso è vulnerabile
e tutto in esso sembra esserlo, quindi qualsiasi finding contro di esso è un falso
positivo per costruzione.
CONTRIBUTING.md contiene tutto: cos'è di solito un
contributo, le invarianti che ./lime audit impone e perché una correzione a una
answer key vale più qui di una nuova funzionalità. La versione breve delle
regole è qui sotto. Tutti coloro che partecipano sono tenuti al
code of conduct.
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
Regole che mantengono il lab pulito:
127.0.0.1.[lime-web]. Non aggiungere una rete
per progetto: troppe reti e Docker esaurisce il pool di indirizzi.[default, lime-web] e mettono il database su
[default] soltanto. Ogni target ottiene il proprio servizio database e volume.[lime-lab] con
un IP statico e non pubblicano alcuna porta sull'host.upstream è obbligatorio. lime doctor fa fallire un target senza di esso e il
manager rifiuta di registrarne uno. Vedi sotto.repo: e compose: nel
manifest e il sorgente viene scaricato a runtime in <target>/src../lime measure <slug>
stampa il blocco resources da incollare. L'installer e il pannello sommano
questi per avvisare qualcuno prima che lo avvii, quindi una stima qui è una bugia là.I target sono software deliberatamente vulnerabile di altre persone, quindi la provenienza è registrata anziché presunta, e il runtime è vincolato anziché fidato. SECURITY.md ha il quadro completo; la versione breve:
./lime pin segnala la deriva.raesene/bwapp (archiviato, ultima ricostruzione
2022, nessuna licenza) e delfer/alpine-ftp-server (un singolo individuo), sono indicati
come tali.no-new-privileges, cap_drop: ALL più un minimo
cap_add per immagine, un tetto di pid e un tetto di memoria. I livelli database stanno su
reti internal: true senza route verso l'esterno.limed detiene il socket Docker, che è root sull'host, quindi richiede un
segreto condiviso su ogni chiamata. Un target compromesso può raggiungerlo e non apprendere nulla../lime audit fallisce su privileged, host networking, un mount del socket in un
target, un bind fuori dal loopback, un'immagine non fissata o un token mancante.Nulla qui difende da un container escape a livello di kernel. Per quello, usa una VM usa e getta.
Quasi tutto qui è stato scritto da qualcun altro, e diversi target non dichiarano alcuna licenza. Quindi accreditare l'autore è un gate obbligatorio, non una convenzione:
target.yml porta un blocco upstream obbligatorio: autore, repo, licenza
e la data in cui abbiamo verificato l'ultima volta che compila. packager registra la persona che
ha containerizzato qualcosa quando differisce da chi l'ha scritto.none declared viene resa come un
avviso, che è anche il segnale di non-ridistribuzione./credits nella UI e ./lime credits elencano ogni target, autore e
licenza. ./lime credits --markdown rigenera la sezione qui sotto.limeyard esegue il lavoro di altre persone. Ogni target e scenario qui sotto è stato costruito da qualcun altro a meno che non dica Clickswave.
| target | autore | licenza | fonte |
|---|---|---|---|
| mirage | Clickswave | MIT | repo |
| target | autore | licenza | fonte |
|---|---|---|---|
| Log4Shell lab | Christophe Tafani-Dereeper (christophetd) | Apache-2.0 | repo |
| target | autore | licenza | fonte |
|---|---|---|---|
| ModSecurity CRS pair | OWASP Core Rule Set project (coreruleset) | Apache-2.0 | repo |
| target | autore | licenza | fonte |
|---|---|---|---|
| AndroGoat | Satish Patnayak | none declared | repo |
| scenario | autore | licenza | fonte |
|---|---|---|---|
| estate | Clickswave | MIT | - |
| target | autore | licenza | fonte |
|---|---|---|---|
| Open services | Clickswave (composition of upstream official images) | mixed, per-image | - |
| target | autore | licenza | fonte |
|---|---|---|---|
| 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 |