Pular para o conteúdo
Novo

DeepSeek V4.1 Flash grátis por 7 dias: 10M tokens por dia após qualquer recarga. Até 30/11.

Como funciona

Como criar uma rede Docker isolada para o seu laboratório

Crie uma rede Docker sem acesso à internet, rode o OWASP Juice Shop nela e publique uma cópia para o navegador só em 127.0.0.1.

Iniciante13 min de leituraPor NotHoly

Um laboratório só é tão seguro quanto os caminhos que entram e saem dele. Se a sua aplicação vulnerável consegue acessar a internet, ela pode fazer chamadas para lugares que você nunca quis tocar. Se ela escuta em todas as interfaces da sua máquina, qualquer notebook na mesma rede Wi-Fi consegue alcançá-la. O Docker dá controle sobre os dois caminhos com alguns comandos. Este guia usa o OWASP Juice Shop como alvo e percorre cada um deles.

Você vai criar uma rede sem acesso para fora, subir o Juice Shop nela, chamar a aplicação a partir de um segundo contêiner e provar que essa rede não alcança a internet. Depois vai rodar uma cópia separada para o seu navegador, publicada apenas na interface de loopback, para que nada na sua rede local consiga vê-la. Isolamento é o primeiro controle de segurança de qualquer laboratório. Todos os exercícios seguintes, da varredura à prática de exploração, dependem disso.

Se você ainda não rodou o Juice Shop, comece por OWASP Juice Shop com Docker. Este artigo pressupõe os conceitos básicos de contêineres explicados nesse guia.

Por que o isolamento vem primeiro

Um laboratório de segurança roda software feito para ser quebrado. O Juice Shop tem falhas intencionais, e é exatamente essa a graça. O risco não está na aplicação em si. O risco está no que acontece quando uma falha, uma configuração errada ou um erro de digitação dá à aplicação, ou a uma ferramenta que você roda contra ela, um caminho para outro lugar.

Três casos mostram como isso dá errado:

  • A aplicação tem um recurso que busca uma URL, e alguém (ou um payload que você testou) faz com que ela requisite um host que você não pretendia. Sem controle de saída, a requisição sai da sua máquina.
  • Um contêiner de ferramenta executa um comando contra um endereço digitado errado. Sem uma fronteira de rede, esse endereço pode pertencer ao seu roteador, à sua empresa ou ao servidor de um desconhecido.
  • Você publica a aplicação com um -p 3000:3000 puro. O Docker passa a aceitar conexões em todas as interfaces do host, então outros dispositivos da sua rede conseguem acessar a aplicação vulnerável.

O isolamento cobre cada um desses casos. Uma rede interna não tem rota para fora. Uma porta presa ao loopback não tem caminho pela LAN. Uma rede dedicada separa os contêineres do laboratório dos seus outros projetos. Nenhum desses passos é difícil. Eles só são fáceis de esquecer quando você está com pressa, e por isso este guia manda verificar cada um.

Redes do Docker em cinco minutos

Você precisa de três ideias antes de os comandos fazerem sentido.

Redes bridge

O Docker conecta contêineres a redes virtuais. O driver padrão é o bridge. Cada contêiner em uma rede bridge recebe o próprio endereço IP em uma sub-rede privada. No Linux, a bridge roda no próprio host. No Windows e no macOS, o Docker Desktop executa o motor dentro de uma máquina virtual Linux, então a bridge fica dentro dessa VM.

Contêineres na bridge padrão conseguem se falar pelo endereço IP, mas não pelo nome. Uma rede bridge criada por você, com docker network create, passa a resolver nomes de contêineres por DNS. O Docker roda um servidor DNS embutido para essas redes. Por isso o contêiner de ferramenta deste guia consegue chamar http://juice:3000 sem depender de um IP que muda depois de um reinício.

Redes internas

Quando você cria uma rede com --internal, o Docker restringe o acesso externo a ela. Segundo a documentação, os contêineres de uma rede interna conversam entre si, mas não com nada fora dela. Isso inclui a internet. Você vai ver isso no passo 4, quando a consulta de nome para um host externo falha.

Portas publicadas

Por padrão, a porta de um contêiner não é publicada para outras máquinas. A opção -p ou --publish muda isso. Ela mapeia um endereço e uma porta do host para uma porta do contêiner, e o endereço que você escolhe define quais interfaces aceitam conexões.

-p 3000:3000 escuta em todas as interfaces do host, inclusive o endereço da sua LAN. -p 127.0.0.1:3000:3000 escuta só no loopback, então apenas programas da mesma máquina conseguem conectar. A documentação do Docker sobre publicação de portas mostra exatamente essa forma. A diferença é um prefixo, e ela importa.

O que você vai montar

O laboratório tem cinco partes:

  • juicenet, uma rede interna sem rota de saída.
  • juice, o Juice Shop conectado somente à juicenet, sem porta publicada.
  • Um contêiner descartável curlimages/curl na juicenet que chama o juice pelo nome.
  • O mesmo contêiner descartável tentando alcançar example.com, o que deve falhar.
  • juice-local, uma segunda instância do Juice Shop publicada em 127.0.0.1:3000 para o seu navegador.

O contêiner juice é o que você isola. O juice-local é uma cópia de conveniência para navegar. Ele roda na rede padrão, então consegue acessar a internet. Tenha isso em mente e não coloque credenciais reais nem dados pessoais na aplicação.

Antes de começar

Você precisa de:

  • Docker instalado e o motor em execução. docker info deve mostrar os detalhes do servidor sem erros. Na máquina usada neste guia, o Docker Engine 29.8.2 rodava no Windows 11 por meio do Docker Desktop.
  • A porta 3000 livre no host para a cópia do navegador.
  • Um shell. Os exemplos usam o Git Bash no Windows 11. Os comandos funcionam do mesmo jeito em shells do macOS e do Linux. No PowerShell, as strings de formato do -w, entre aspas simples, precisam de outra forma de citação.

Confira os nomes antes de escolhê-los. Outros projetos da sua máquina podem já usar nomes como juice ou juicenet. Rode isto primeiro:

bash
docker ps -a --format '{{.Names}}'
docker network ls --format '{{.Name}}'

Se alguma das listas mostrar juice, juice-local ou juicenet, escolha outros nomes ou remova antes os antigos. Não apague nada que você não criou.

Confira também a imagem que você vai rodar. O Juice Shop publica rótulos de versão na imagem:

bash
docker image inspect bkimminich/juice-shop --format '{{index .Config.Labels "org.opencontainers.image.version"}}'

Esse comando falha até a imagem estar local, porque o primeiro docker run é que a baixa. Na máquina usada neste guia, a imagem informou a versão 20.3.0. A sua pode informar outra, e vale anotar qual é.

Passo 1: criar a rede interna

bash
docker network create --internal juicenet

O Docker imprime o ID da nova rede. O seu será diferente. Foi isto que apareceu na máquina de teste:

text
44a865311b8b0df7808f81dccd11f4b50ca38800d7a38a6d17bc80fc104dadf9

Confirme que a flag foi aplicada:

bash
docker network inspect juicenet --format '{{.Internal}}'
text
true

Se a saída for false, você criou a rede sem --internal. Remova com docker network rm juicenet e crie de novo. Não continue antes de ver true, porque todos os testes seguintes partem do pressuposto de que a flag está ativa.

Passo 2: subir o Juice Shop na rede interna

bash
docker run -d --name juice --network juicenet bkimminich/juice-shop

Cada opção tem uma função. -d roda o contêiner em segundo plano. --name juice dá a ele um nome DNS que outros contêineres da juicenet conseguem resolver. --network juicenet conecta o contêiner só a essa rede interna. O comando não tem -p, então o contêiner não publica porta alguma para o host.

O Docker imprime o ID do contêiner:

text
d9f182bbbec2dc8db7dcde02f3e94b054274b6cf8addb1a5f994e9ee23ddebf8

A aplicação dentro do contêiner escuta na porta 3000. O Juice Shop precisa de um tempo para iniciar depois que o contêiner sobe, então não teste logo em seguida. Use um laço que verifica se um contêiner de ferramenta recebe HTTP 200:

bash
for i in $(seq 1 30); do
  s=$(docker run --rm --network juicenet curlimages/curl -s -o /dev/null -w '%{http_code}\n' http://juice:3000)
  echo "tentativa $i: $s"
  [ "$s" = "200" ] && break
  sleep 3
done

Enquanto a aplicação sobe, o laço imprime 000, que significa que o curl não conseguiu conectar. Na máquina de teste saiu 000 quatro vezes e 200 na quinta tentativa. A sua pode levar mais ou menos tentativas. O laço desiste depois de 30. Se você nunca vir um 200, confira docker logs juice para ver os erros.

Passo 3: alcançar a aplicação a partir de um contêiner de ferramenta

bash
docker run --rm --network juicenet curlimages/curl -s -o /dev/null -w '%{http_code}\n' http://juice:3000
text
200

Leia cada parte do comando. --rm remove o contêiner de ferramenta quando ele termina. --network juicenet coloca a ferramenta na mesma rede interna da aplicação. curlimages/curl é uma imagem pequena que contém o curl. http://juice:3000 usa o nome do contêiner, que o Docker resolve dentro da juicenet. A flag -s esconde a barra de progresso, -o /dev/null descarta o corpo da resposta e -w imprime só o código de status.

Esse é o padrão que você vai reutilizar. Um scanner, um proxy ou um script entra na rede do laboratório e aponta para o contêiner pelo nome. Nada no seu host precisa rotear para a aplicação.

A tag da imagem aqui não está fixada, então o Docker baixa o que for latest no momento. Para um laboratório que você pretende repetir, fixe a tag depois de testá-la. O princípio é o mesmo.

Passo 4: provar que a rede não tem acesso à internet

Um teste que passa só significa algo quando um teste que falha vem depois. O passo 3 mostrou que o contêiner de ferramenta alcança a aplicação, então a rede funciona. Agora verifique o outro sentido:

bash
docker run --rm --network juicenet curlimages/curl -sS -m 5 https://example.com; echo "exit=$?"
text
curl: (28) Resolving timed out after 5000 milliseconds
exit=28

A flag -m 5 limita a requisição a cinco segundos. -sS esconde a barra de progresso, mas mantém os erros visíveis. O código de saída 28 é o código de timeout do curl. A falha acontece na resolução de nome. A consulta de example.com nunca recebe resposta, o que combina com uma rede sem rota para fora, então o curl nem chega a abrir uma conexão.

Esse é o resultado que você quer. O laboratório alcança os próprios contêineres e não alcança o exterior. Anote as saídas dos passos 3 e 4 no seu caderno de laboratório, junto com a data e a versão do Docker. Esse registro ajuda quando você voltar ao laboratório e algo tiver mudado.

Passo 5: rodar uma cópia para o navegador, presa ao loopback

Seu navegador não resolve o nome juice, e o contêiner juice não publica porta, então ele não pode ser acessado a partir do host. Para navegar, suba uma segunda cópia e publique-a no loopback:

bash
docker run -d --name juice-local -p 127.0.0.1:3000:3000 bkimminich/juice-shop

A opção -p 127.0.0.1:3000:3000 mapeia a porta 3000 da interface de loopback para a porta 3000 dentro do contêiner. Essa cópia usa a bridge padrão, não a juicenet, então não está isolada da internet. É uma escolha consciente para uma cópia de conveniência. Se você precisa de uma configuração mais restrita, terá de desenhar um arranjo separado e testá-lo.

A cópia demora um pouco para subir. Na máquina de teste, a cópia respondeu 200 na décima tentativa. Use o mesmo tipo de laço de antes, apontado para o host:

bash
for i in $(seq 1 30); do
  s=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/)
  echo "tentativa $i: $s"
  [ "$s" = "200" ] && break
  sleep 3
done

Confirme onde a porta está presa:

bash
docker port juice-local
text
3000/tcp -> 127.0.0.1:3000

Agora confira a LAN. Descubra o endereço IPv4 da sua máquina na rede local. Use ipconfig no Windows ou ip addr no Linux. Depois, requisite a aplicação nesse endereço:

bash
curl -s -m 5 -o /dev/null -w '%{http_code}\n' http://SEU-IP-DA-LAN:3000/

Troque SEU-IP-DA-LAN pelo seu endereço. Na máquina de teste, essa requisição retornou 000, o que significa que o curl não conseguiu conectar, e ele saiu com código 7. A mesma requisição para 127.0.0.1 retornou 200. Essa diferença é o ponto central da publicação no loopback.

Agora abra http://127.0.0.1:3000 no navegador. Você deve ver a página inicial do Juice Shop. Use a aplicação apenas nessa mesma máquina.

Rode docker ps para ver os dois contêineres lado a lado:

bash
docker ps --filter name=juice --format 'table {{.Names}}	{{.Ports}}'
text
NAMES         PORTS
juice-local   127.0.0.1:3000->3000/tcp
juice         3000/tcp

A linha do juice mostra 3000/tcp sem endereço do host. Isso quer dizer que a porta existe dentro da rede, mas não está mapeada para o host. A linha do juice-local mostra a publicação no host.

Passo 6: limpar

Pare e remova o laboratório. Os contêineres precisam sair antes da rede, porque o Docker se recusa a remover uma rede que ainda tem contêineres conectados:

bash
docker rm -f juice juice-local && docker network rm juicenet
text
juice
juice-local
juicenet

O Docker imprime os nomes que removeu. Depois confirme que nada sobrou:

bash
docker ps -a --filter name=juice --format '{{.Names}}'
docker network ls --filter name=juicenet --format '{{.Name}}'

Os dois comandos não devem imprimir nada. Filtros casam parte do nome, então leia a saída antes de assumir que ela está completa, e remova só o que você iniciou.

O --rm só remove os contêineres descartáveis de ferramenta. Contêineres iniciados com -d ficam até você removê-los, e é por isso que este passo existe.

Erros comuns

Publicar sem endereço. -p 3000:3000 escuta em todas as interfaces do host. Use o prefixo 127.0.0.1: para qualquer aplicação do laboratório que você acesse da própria máquina. Confira o resultado com docker port.

Esquecer o --internal. A rede funciona sem a flag, e nada avisa. Rode docker network inspect juicenet --format '{{.Internal}}' e procure true.

Conectar a aplicação à rede padrão. Um contêiner iniciado sem --network entra na bridge padrão, que alcança o exterior. Se a aplicação deve ficar isolada, dê nome à rede toda vez.

Confiar em um teste que falhou. Um erro no passo 4 pode vir do DNS, de um proxy, de uma VPN ou de um erro de digitação. O passo 3 é o controle que mostra se o teste está medindo a coisa certa.

Usar o nome a partir do host. O nome juice só resolve para contêineres da juicenet. O seu navegador não consegue usá-lo. Por isso existe a cópia para o navegador.

Deixar contêineres rodando. Um contêiner em segundo plano continua consumindo recursos e mantém a porta presa. Termine cada sessão com o passo de limpeza e rode docker ps depois.

O que este laboratório não prova

A opção de rede interna é um controle, e ele tem limites.

Ele não impede uma ferramenta na juicenet de alcançar todos os outros contêineres dessa rede. Contêineres da mesma rede conseguem se falar, e é isso que faz o passo 3 funcionar. Mantenha serviços não relacionados fora da rede do laboratório.

A publicação no loopback protege você de outras máquinas da sua LAN. Ela não protege contra programas que já rodam no seu próprio computador, porque esses programas também conseguem conectar em 127.0.0.1.

O teste verifica um nome e um host externo. Ele não prova que todos os caminhos de saída estão fechados. O comportamento do Docker pode variar entre Docker Desktop, motores Linux e configurações sem root. Rode as verificações de novo na máquina que você realmente usa.

Por fim, este laboratório só mostra que o isolamento funciona. Ele não mostra que o Juice Shop é seguro para ser exposto. Não é, e esse é o motivo para mantê-lo isolado.

Como ir além

Depois que o laboratório funcionar, algumas pequenas mudanças o tornam mais útil:

  • Escreva um script curto que rode as verificações dos passos 3 a 5 e mostre passou ou falhou em cada uma. Rode-o toda vez que subir o laboratório.
  • Adicione um segundo serviço à juicenet, como um banco de dados, e confirme que a aplicação o alcança pelo nome. É assim que os laboratórios com vários contêineres são montados.
  • Leia a documentação de rede do Docker sobre as opções de driver que você ainda não usou, como bridges definidas pelo usuário com sub-redes personalizadas.
  • Aprenda o básico de descoberta de hosts antes de varrer qualquer coisa. O próximo artigo trata de Nmap básico no seu próprio laboratório. Varra apenas redes e hosts que sejam seus ou para os quais você tenha permissão por escrito. A rede juicenet é um bom primeiro alvo, porque você controla todos os contêineres dela.

Fontes

Fontes

  1. Docker Engine networking overview
  2. docker network create (CLI reference)
  3. Docker port publishing and mapping
  4. OWASP Juice Shop (GitHub repository)

Mais em Laboratórios de hacking

Laboratórios de hacking

Como escrever um bom relatório de pentest

Estruture um relatório de pentest e escreva um achado que outra pessoa consiga reproduzir, com evidência, impacto, vetor CVSS v4.0 e correção.

Intermediário9 min de leitura

Laboratórios de hacking

Como montar um laboratório doméstico dentro da lei

Monte um laboratório que você pode atacar: arquivo de escopo, portas do Docker no loopback, rede isolada, caderno de notas e desmontagem limpa.

Iniciante4 min de leitura