Laboratorio Docker deliberadamente vulnerable con un dominio DNS enrutable y claves de respuesta legibles por máquina por objetivo, que puntúa localmente la precisión, la exhaustividad y el alcance del escáner.
Un laboratorio de pruebas de seguridad. Una flota de objetivos deliberadamente vulnerables, un entorno de red enrutable con DNS autoritativo para el descubrimiento de activos que enumerar, una clave de respuestas legible por máquina por objetivo y un panel de control para gestionarlo todo.
Todo aquí es deliberadamente vulnerable. Solo para pruebas locales. No lo expongas a internet ni a una red no confiable. Los puertos publicados se enlazan a
127.0.0.1; los objetivos del laboratorio no se enlazan a nada en absoluto.Nuestro propio escáner obtiene RCE dentro de estos contenedores a propósito, por lo que el contenedor se trata como un límite de seguridad: cada imagen está fijada por digest, cada servicio elimina todas las capacidades y añade de vuelta un mínimo, y
./lime auditlo hace cumplir. Lee SECURITY.md antes de la primera ejecución, incluida la parte sobre lo que el aislamiento de contenedores no cubre.
El panel de control en http://127.0.0.1:7000, mostrando el laboratorio mientras se ejecuta.
limeyard era vuln_apps. Se renombró porque dejó de ser una carpeta de
aplicaciones: ahora contiene servicios desnudos, una zona DNS, un par de WAF, un objetivo de precisión
y fixtures de APK, ninguno de los cuales son aplicaciones.
La flota antigua puntuaba 9 de 9, cero fallos, cero falsos positivos. Un benchmark que no puede fallar no puede detectar una regresión. Tres cosas estaban mal estructuralmente:
127.0.0.1:70xx,
así que la enumeración de subdominios no tenía nada que enumerar, el escaneo de puertos recibía
su respuesta, y el fingerprinting de servicios nunca veía un demonio no HTTP.Necesitas Docker (con el plugin compose) y git. Nada más.
curl -fsSL https://raw.githubusercontent.com/clickswave/limeyard/main/install.sh | bash
Eso clona el laboratorio en ./limeyard, escribe su .env con un token de API
nuevo, construye e inicia el plano de control, y luego pregunta qué ejecutar. Antes de
que empiece nada muestra cuánto cuesta la selección, medido en reposo en la
máquina de referencia, frente a lo que tu máquina tiene libre:
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]
No interactivo: curl ... | bash -s -- --light --yes (o --all,
--none, --pick dvwa,juice-shop,estate). Coloca el checkout en otro lugar con
LIMEYARD_DIR=/path.
El panel está entonces en http://127.0.0.1:7000, y las mismas cosas 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, sin el instalador:
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
Cada manifiesto lleva un bloque resources medido (contenedores, RAM en reposo,
disco de imágenes, CPU en reposo). El asistente, la franja de selección del panel y la página de cada
objetivo suman a partir de él, así que la estimación es la misma en todas partes.
Dos, y solo dos.
main es lo que clona el instalador y lo que obtienes si no haces nada.
Se mueve por pull request, nunca por push directo.dev es la rama por defecto y donde aterriza el trabajo. Abre pull requests
contra ella.| target | una cosa bajo prueba, de un kind declarado. Posee un archivo compose, setup opcional, y su propia clave de respuestas. Se ejecuta como su propio proyecto compose aislado, así que dos objetivos que usan Postgres nunca comparten uno |
| scenario | varios objetivos conectados en una topología de red con DNS autoritativo. Contra lo que se puntúa el descubrimiento de activos |
| truth | la clave de respuestas legible por máquina. Ver truth/schema.md |
| doctor | una lista de comprobaciones con un veredicto cada una: entorno, atribución, cadena de suministro, hardening. Las comprobaciones con un remedio inequívoco llevan un arreglo de un clic en el panel (/doctor) y --fix en la CLI: crear redes, reclamar disco, fijar imágenes, obtener fuentes, reverificar objetivos en ejecución, reescribir enlaces fuera de loopback. La atribución, los conflictos de puertos y el hardening necesitan una persona |
Kinds: 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 inicia dos contenedores y nada más: limeyard_control
(el demonio limed, que sostiene el socket de Docker) y limeyard_ui (el panel
SvelteKit). Ambos se enlazan solo a loopback. El panel está en http://127.0.0.1:7000 y la
API cruda en http://127.0.0.1:7099. Ambos necesitan .env, y LIME_TOKEN en él es
obligatorio: el panel sostiene el token del lado del servidor y el navegador nunca lo ve.
| página | para qué sirve |
|---|---|
| Targets | cada objetivo, filtrar por tipo y estado, ordenar, selección múltiple con Start, Stop, Restart. Haz clic en una fila para el objetivo |
| Target | datos (dirección, credenciales, stack, upstream, verificación, digests de imágenes), el log en vivo, y la clave de respuestas con sus negativos |
| Scenarios | el estate: resolver, zonas, subred, y una tabla de hosts con el estado por contenedor. Levantar, bajar, reiniciar |
| Scorecard | la última ejecución con deltas, cobertura por clase y por objetivo, ids perdidos, un historial que puedes ver, y un diff de dos ejecuciones |
| Ports | qué se enlaza en localhost, y qué solo existe en el bridge del laboratorio |
| Doctor | una lista de comprobaciones con un veredicto cada una. Fix y Fix all muestran primero los comandos exactos y las ediciones de archivos, calculados a partir del laboratorio tal como está, y los ejecutan al confirmar. Un arreglo que deja su comprobación fallando lo dice |
| Credits | quién escribió cada objetivo y bajo qué licencia |
El panel se actualiza solo: limed transmite las transiciones de estado por SSE, así que un objetivo iniciado desde la CLI aparece sin refrescar. La cabecera lleva la CPU, RAM y disco libre del host desde el mismo stream, coloreados solo cuando merece la pena notarlos. Cada tabla se ordena haciendo clic en la cabecera de una columna; un segundo clic invierte la dirección.
Tras un cambio en el panel o el demonio, reconstruye el par:
docker compose up -d --build
Para trabajar en el panel contra un demonio en ejecución sin reconstruir la imagen:
cd control/ui && npm install
LIMED_URL=http://127.0.0.1:7099 LIME_TOKEN=<from .env> npm run dev # :7000
Cada llamada excepto /api/health necesita 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
Tres niveles, porque uno era el problema original.
lime-web bridge. Objetivos web y API, publicados en 127.0.0.1:70xx.lime-lab bridge, 10.66.0.0/16, IPs estáticas, sin enlace al host. Un escáner
se une a esta red como un contenedor y ve una subred real con hosts reales y
puertos reales en lugar de una lista de puertos de loopback.lime-edge el nivel WAF, con el origen alcanzable pero ausente del DNS.El DNS es BIND autoritativo en zonas .test (RFC 6761 la reserva). Los alias de red de
Docker deliberadamente no son la fuente de verdad: nunca aparecen en una transferencia de zona,
y harían que la topología discrepara con la clave de respuestas.
| rango | uso |
|---|---|
| 7000 | la UI |
| 7099 | API de limed |
| 7001-7099 | objetivos web y api |
| 7100-7199 | suites de benchmark |
| 7200-7299 | laboratorios CVE |
| 7300-7399 | objetivos edge y control |
| 5353 | DNS del laboratorio |
| ninguno | objetivos service y estate, solo direcciones lime-lab |
Cada objetivo envía un truth.yml. El scorer convierte los hallazgos en precisión,
recall y F1, por objetivo y por clase:
curl -s -XPOST localhost:7099/api/score -H 'Content-Type: application/json' \
-d '{"tool":"crossfyre","save":true,"findings":[...]}'
Se cuentan tres cosas, no una. Recall: encontramos lo que está ahí.
Precision: evitamos reportar lo que no está, medido contra las
entradas negative que lleva cada clave de respuestas. Scope: lo que correctamente
no intentamos, registrado para que nadie lo re-litigue cada trimestre.
El objetivo mirage existe solo para lo segundo. Nada en él es vulnerable
y todo en él parece que lo es, así que cualquier hallazgo contra él es un falso
positivo por construcción.
CONTRIBUTING.md tiene todo: qué es normalmente una contribución,
las invariantes que hace cumplir ./lime audit, y por qué una corrección a una
clave de respuestas vale más aquí que una nueva funcionalidad. La versión corta de las
reglas está abajo. Todos los que participan están sujetos al
código de conducta.
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
Reglas que mantienen el laboratorio limpio:
127.0.0.1.[lime-web]. No añadas una red
por proyecto: demasiadas redes y Docker se queda sin pool de direcciones.[default, lime-web] y ponen la base de datos en
[default] solo. Cada objetivo obtiene su propio servicio de base de datos y volumen.[lime-lab] con
una IP estática y no publican ningún puerto de host.upstream es obligatorio. lime doctor falla un objetivo sin él y el
manager se niega a registrar uno. Ver abajo.repo: y compose: en el
manifiesto y la fuente se obtiene en tiempo de ejecución en <target>/src en su lugar../lime measure <slug>
imprime el bloque resources para pegar. El instalador y el panel suman
estos para advertir a alguien antes de que lo inicie, así que una suposición aquí es una mentira allí.Los objetivos son software deliberadamente vulnerable de otras personas, así que la procedencia se registra en lugar de asumirse, y el runtime se restringe en lugar de confiarse. SECURITY.md tiene el panorama completo; la versión corta:
./lime pin reporta la deriva.raesene/bwapp (archivado, última reconstrucción
2022, sin licencia) y delfer/alpine-ftp-server (un individuo), se nombran
como tales.no-new-privileges, cap_drop: ALL más un cap_add
mínimo por imagen, un techo de pids y un techo de memoria. Los niveles de base de datos se sitúan en
redes internal: true sin ruta de salida.limed sostiene el socket de Docker, que es root en el host, así que requiere un
secreto compartido en cada llamada. Un objetivo comprometido puede alcanzarlo y no aprender nada../lime audit falla con privileged, networking de host, un montaje de socket en un
objetivo, un enlace fuera de loopback, una imagen no fijada o un token ausente.Nada aquí defiende contra un escape de contenedor a nivel de kernel. Para eso, usa una VM desechable.
Casi todo aquí fue escrito por otra persona, y varios objetivos no declaran ninguna licencia. Así que acreditar al autor es una puerta dura, no una convención:
target.yml lleva un bloque upstream obligatorio: autor, repo, licencia,
y la fecha en que verificamos por última vez que construye. packager registra a la persona que
containerizó algo cuando eso difiere de quien lo escribió.none declared se renderiza como una
advertencia, que es también la señal de no redistribuir./credits en la UI y ./lime credits listan cada objetivo, autor y
licencia. ./lime credits --markdown regenera la sección de abajo.limeyard ejecuta el trabajo de otras personas. Cada objetivo y escenario de abajo fue construido por otra persona a menos que diga Clickswave.
| objetivo | autor | licencia | fuente |
|---|---|---|---|
| mirage | Clickswave | MIT | repo |
| objetivo | autor | licencia | fuente |
|---|---|---|---|
| Log4Shell lab | Christophe Tafani-Dereeper (christophetd) | Apache-2.0 | repo |
| objetivo | autor | licencia | fuente |
|---|---|---|---|
| ModSecurity CRS pair | OWASP Core Rule Set project (coreruleset) | Apache-2.0 | repo |
| objetivo | autor | licencia | fuente |
|---|---|---|---|
| AndroGoat | Satish Patnayak | none declared | repo |
| escenario | autor | licencia | fuente |
|---|---|---|---|
| estate | Clickswave | MIT | - |
| objetivo | autor | licencia | fuente |
|---|---|---|---|
| Open services | Clickswave (composition of upstream official images) | mixed, per-image | - |
| objetivo | autor | licencia | fuente |
|---|---|---|---|
| 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 |