Laboratório Docker deliberadamente vulnerável com um domínio DNS roteável e chaves de resposta legíveis por máquina por alvo, avaliando localmente a precisão, a revocação e o escopo do scanner.
Um laboratório de testes de segurança. Uma frota de alvos deliberadamente vulneráveis, uma infraestrutura de rede roteável com DNS autoritativo para descoberta de ativos a enumerar, uma chave de respostas legível por máquina por alvo, e um painel de controlo para gerir tudo.
Tudo aqui é deliberadamente vulnerável. Apenas para testes locais. Não o exponha à internet nem a uma rede não confiável. As portas publicadas ligam-se a
127.0.0.1; os alvos do laboratório não se ligam a nada.O nosso próprio scanner obtém RCE dentro destes contentores de propósito, por isso o contentor é tratado como uma fronteira de segurança: cada imagem é fixada por digest, cada serviço remove todas as capabilities e adiciona de volta o mínimo, e
./lime auditimpõe isso. Leia o SECURITY.md antes da primeira execução, incluindo a parte sobre o que o isolamento de contentores não cobre.
O painel de controlo em http://127.0.0.1:7000, mostrando o laboratório em execução.
O limeyard era vuln_apps. Foi renomeado porque deixou de ser uma pasta de
aplicações: agora contém serviços simples, uma zona DNS, um par de WAFs, um alvo de precisão
e fixtures de APK, nenhum dos quais são aplicações.
A frota antiga pontuou 9 de 9, zero falhas, zero falsos positivos. Um benchmark que não pode falhar não consegue detetar uma regressão. Três coisas estavam estruturalmente erradas:
127.0.0.1:70xx,
por isso a enumeração de subdomínios não tinha nada para enumerar, o port scanning recebia
a sua resposta, e a identificação de serviços nunca via um daemon não-HTTP.Precisa de Docker (com o plugin compose) e git. Nada mais.
curl -fsSL https://raw.githubusercontent.com/clickswave/limeyard/main/install.sh | bash
Isso clona o laboratório para ./limeyard, escreve o seu .env com um novo token de API,
constrói e inicia o plano de controlo, e depois pergunta o que executar. Antes de
qualquer coisa iniciar, mostra quanto custa a seleção, medido em idle na
máquina de referência, em comparação com o que a sua máquina tem livre:
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]
Não interativo: curl ... | bash -s -- --light --yes (ou --all,
--none, --pick dvwa,juice-shop,estate). Coloque o checkout noutro sítio com
LIMEYARD_DIR=/path.
O painel fica então em http://127.0.0.1:7000, e as mesmas coisas à mão:
./lime setup # o assistente outra vez, a qualquer momento
./lime start --all # todos os alvos leves
./lime start crapi --heavy # um pesado, explicitamente
./lime scenario-up estate # a infraestrutura de rede: DNS, vhosts, serviços
./lime status # o que está ativo
./lime stop --all --heavy # tudo desligado; imagens e volumes permanecem
./lime credits # quem escreveu cada alvo, e sob que licença
./lime doctor # verificações de ambiente, atribuição e disco
./lime doctor --fix # aplicar o remédio automático de cada verificação, depois reverificar
./lime audit # endurecimento de contentores + invariantes da cadeia de fornecimento
./lime pin # reportar desvio de imagens em relação ao registry
À mão, sem o 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 # obrigatório, ver SECURITY.md
docker compose up -d --build # plano de controlo + UI em http://127.0.0.1:7000
./lime setup
Cada manifesto carrega um bloco resources medido (contentores, RAM em idle,
disco de imagens, CPU em idle). O assistente, a faixa de seleção do painel e a página
de cada alvo somam a partir dele, por isso a estimativa é a mesma em todo o lado.
Duas, e apenas duas.
main é o que o instalador clona e o que obtém se não fizer nada.
Move-se por pull request, nunca por push direto.dev é a branch predefinida e onde o trabalho aterra. Abra pull requests
contra ela.| target | uma coisa sob teste, de um kind declarado. Possui um ficheiro compose, setup opcional, e a sua própria chave de respostas. Corre como o seu próprio projeto compose isolado, por isso dois alvos a usar Postgres nunca partilham um |
| scenario | vários alvos ligados numa topologia de rede com DNS autoritativo. Contra o que a descoberta de ativos é pontuada |
| truth | a chave de respostas legível por máquina. Ver truth/schema.md |
| doctor | uma lista de verificações com um veredicto cada: ambiente, atribuição, cadeia de fornecimento, endurecimento. Verificações com um remédio inequívoco têm um fix de um clique no painel (/doctor) e --fix na CLI: criar redes, recuperar disco, fixar imagens, obter fontes, reverificar alvos em execução, reescrever binds fora do loopback. Atribuição, conflitos de portas e endurecimento precisam de uma pessoa |
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/ o daemon: CLI + HTTP API + scorer
control/ui/ o painel de controlo SvelteKit
truth/ o contrato, e scorecards datados
docker compose up -d inicia dois contentores e nada mais: limeyard_control
(o daemon limed, que detém o socket Docker) e limeyard_ui (o painel
SvelteKit). Ambos ligam-se apenas ao loopback. O painel está em http://127.0.0.1:7000 e a
API crua em http://127.0.0.1:7099. Ambos precisam de .env, e LIME_TOKEN nele é
obrigatório: o painel detém o token do lado do servidor e o browser nunca o vê.
| página | para que serve |
|---|---|
| Targets | cada alvo, filtrar por kind e estado, ordenar, multi-seleção com Start, Stop, Restart. Clique numa linha para o alvo |
| Target | factos (endereço, credenciais, stack, upstream, verificação, digests de imagens), o log em direto, e a chave de respostas com os seus negativos |
| Scenarios | a infraestrutura: resolver, zonas, sub-rede, e uma tabela de hosts com o estado por contentor. Levantar, baixar, reiniciar |
| Scorecard | a última execução com deltas, cobertura por classe e por alvo, ids falhados, um histórico que pode ver, e um diff de duas execuções |
| Ports | o que se liga em localhost, e o que só existe na bridge do laboratório |
| Doctor | uma lista de verificações com um veredicto cada. Fix e Fix all mostram primeiro os comandos exatos e edições de ficheiros, calculados a partir do laboratório tal como está, e executam-nos após confirmação. Um fix que deixa a sua verificação a falhar di-lo |
| Credits | quem escreveu cada alvo e sob que licença |
O painel atualiza-se sozinho: o limed transmite transições de estado por SSE, por isso um alvo iniciado a partir da CLI aparece sem refresh. O cabeçalho transporta a CPU, RAM e disco livre do host a partir do mesmo stream, coloridos apenas quando vale a pena notar. Cada tabela ordena ao clicar no cabeçalho de uma coluna; um segundo clique inverte a direção.
Após uma alteração ao painel ou ao daemon, reconstrua o par:
docker compose up -d --build
Para trabalhar no painel contra um daemon em execução sem reconstruir a imagem:
cd control/ui && npm install
LIMED_URL=http://127.0.0.1:7099 LIME_TOKEN=<from .env> npm run dev # :7000
Todas as chamadas exceto /api/health precisam de 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
Três níveis, porque um era o problema original.
lime-web bridge. Alvos web e API, publicados em 127.0.0.1:70xx.lime-lab bridge, 10.66.0.0/16, IPs estáticos, sem binding ao host. Um scanner
junta-se a esta rede como contentor e vê uma sub-rede real com hosts reais e
portas reais em vez de uma lista de portas de loopback.lime-edge o nível WAF, com a origem alcançável mas ausente do DNS.O DNS é BIND autoritativo em zonas .test (a RFC 6761 reserva-o). Os aliases de rede
Docker deliberadamente não são a fonte de verdade: nunca aparecem numa transferência de zona,
e fariam a topologia discordar da chave de respostas.
| intervalo | uso |
|---|---|
| 7000 | a UI |
| 7099 | API do limed |
| 7001-7099 | alvos web e api |
| 7100-7199 | suites de benchmark |
| 7200-7299 | laboratórios CVE |
| 7300-7399 | alvos edge e control |
| 5353 | DNS do laboratório |
| nenhuma | alvos service e estate, apenas endereços lime-lab |
Cada alvo inclui um truth.yml. O scorer transforma os findings em precisão,
recall e F1, por alvo e por classe:
curl -s -XPOST localhost:7099/api/score -H 'Content-Type: application/json' \
-d '{"tool":"crossfyre","save":true,"findings":[...]}'
Três coisas são contadas, não uma. Recall: encontrámos o que lá está.
Precisão: evitámos reportar o que não está, medido contra as
entradas negative que cada chave de respostas carrega. Âmbito: o que corretamente não
tentámos, registado para que ninguém o reabra a cada trimestre.
O alvo mirage existe apenas para o segundo. Nada nele é vulnerável
e tudo nele parece que é, por isso qualquer finding contra ele é um falso
positivo por construção.
O CONTRIBUTING.md tem tudo: o que uma contribuição
normalmente é, os invariantes que ./lime audit impõe, e porque uma correção a uma
chave de respostas vale mais aqui do que uma nova funcionalidade. A versão curta das
regras está abaixo. Todos os que participam estão vinculados ao
código de conduta.
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
Regras que mantêm o laboratório limpo:
127.0.0.1.[lime-web]. Não adicione uma rede
por projeto: demasiadas redes e o Docker fica sem pool de endereços.[default, lime-web] e colocam a base de dados em
[default] apenas. Cada alvo tem o seu próprio serviço de base de dados e volume.[lime-lab] com
um IP estático e não publicam nenhuma porta de host.upstream é obrigatório. lime doctor falha um alvo sem ele e o
gestor recusa registar um. Ver abaixo.repo: e compose: no
manifesto e a fonte é obtida em runtime para <target>/src em vez disso../lime measure <slug>
imprime o bloco resources para colar. O instalador e o painel somam
estes para avisar alguém antes de o iniciar, por isso um palpite aqui é uma mentira ali.Os alvos são software deliberadamente vulnerável de outras pessoas, por isso a proveniência é registada em vez de assumida, e o runtime é restringido em vez de confiado. O SECURITY.md tem o quadro completo; a versão curta:
./lime pin reporta desvios.raesene/bwapp (arquivado, último rebuild
em 2022, sem licença) e delfer/alpine-ftp-server (um indivíduo), são nomeados
como tal.no-new-privileges, cap_drop: ALL mais um mínimo
cap_add por imagem, um teto de pids e um teto de memória. Os níveis de base de dados ficam em
redes internal: true sem rota para fora.limed detém o socket Docker, que é root no host, por isso exige um
segredo partilhado em cada chamada. Um alvo comprometido pode alcançá-lo e não aprender nada../lime audit falha em privileged, host networking, uma montagem de socket num
alvo, um bind fora do loopback, uma imagem não fixada ou um token em falta.Nada aqui defende contra uma fuga de contentor ao nível do kernel. Para isso, use uma VM descartável.
Quase tudo aqui foi escrito por outra pessoa, e vários alvos não declaram licença alguma. Por isso creditar o autor é um requisito obrigatório, não uma convenção:
target.yml carrega um bloco upstream obrigatório: autor, repo, licença,
e a data em que verificámos pela última vez que compila. packager regista a pessoa que
containerizou algo quando isso difere de quem o escreveu.none declared é apresentada como um
aviso, que é também o sinal de não-redistribuir./credits na UI e ./lime credits listam cada alvo, autor e
licença. ./lime credits --markdown regenera a secção abaixo.O limeyard executa o trabalho de outras pessoas. Cada alvo e cenário abaixo foi construído por outra pessoa a menos que diga Clickswave.
| alvo | autor | licença | fonte |
|---|---|---|---|
| mirage | Clickswave | MIT | repo |
| alvo | autor | licença | fonte |
|---|---|---|---|
| Log4Shell lab | Christophe Tafani-Dereeper (christophetd) | Apache-2.0 | repo |
| alvo | autor | licença | fonte |
|---|---|---|---|
| ModSecurity CRS pair | OWASP Core Rule Set project (coreruleset) | Apache-2.0 | repo |
| alvo | autor | licença | fonte |
|---|---|---|---|
| AndroGoat | Satish Patnayak | none declared | repo |
| cenário | autor | licença | fonte |
|---|---|---|---|
| estate | Clickswave | MIT | - |
| alvo | autor | licença | fonte |
|---|---|---|---|
| Open services | Clickswave (composition of upstream official images) | mixed, per-image | - |
| alvo | autor | licença | 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 |