• Nenhum resultado encontrado

Limitação de Recursos

No documento Execução segura com contentores (páginas 59-71)

5.1 Configurações Seguras

5.1.3 Limitação de Recursos

O Docker disponibiliza, através dos cgroups, a possibilidade de limitar os recursos que são atribuídos a um contentor no momento em que é lançado. É possível efetuar um elevado número de configurações, desde escolher em que processador o contentor é executado, até à velocidade de leitura e escrita permitida. Enquanto que a implementação da maior parte destas restrições é aconselhável, isso nem sempre é possível. Algumas aplicações, como mySQL, necessitam de um elevado desempenho de I/O, enquanto que outras, como o Kafka/ZooKeeper, precisam de grande elasticidade. Devido a isto, é difícil encontrar uma configuração de cgroups que seja ao mesmo tempo segura e genérica.

Para tentar evitar o pior tipo de ataques, e ao mesmo tempo manter alguma flexibilidade na configuração, aconselha-se a implementação de restrições, pelo menos, a nível de utilização de CPU, número de processos, quantidade de memória e velocidade de I/O. No exemplo seguinte, é especificado um limite de utilização do CPU, um número de processos nunca superior a 10, 100 MB de limite de memória, e uma velocidade de escrita e leitura nunca superior a 50 MB s. O limite na utilização do CPU depende do número de processadores disponíveis. No caso de só haver 1 CPU, 0.1 corresponde a 10% de utilização. Se houverem 2 CPU, 0.1 corresponde a 5% da utilização total, e 2.0 corresponderia a 100%.

$ docker run -it --rm --cpus="0.1" \ -m 100mb --ulimit nproc=10 \ --device-write-bps /dev/sda:50mb \ --device-read-bps /dev/sda:50mb \ --name ubuntu ubuntu /bin/bash

Quando a aplicação dentro do contentor não necessita de criar ficheiros temporários, é recomendado trocar a configuração da velocidade de escrita, por uma restrição de escrita. Esta configuração é importante quando se montam no contentor diretórios do hospedeiro que não necessitam de permissões de escrita.

5.1.4 Encobrimento da Rede

Uma das grandes vantagens da utilização de contentores é a elasticidade que conferem aos sistemas que os utilizam, o que significa uma elevada densidade de contentores em execução num determinado instante. Isto leva a que os sistemas que empregam tecnologias de contentorização em grande número sejam um alvo muito apetecível por parte de atacantes que têm como objetivo, por exemplo, criar bot nets.

Este tipo de ataques, assim como o estabelecimento de reverse shells e arp spoofing, requerem que o contentor tenha a capacidade de interagir com a rede e com outros contentores. Adicionalmente, um contentor comprometido pode conseguir explorar os serviços e dados disponíveis em outros contentores, podendo assim efetuar exfiltração de informação. Para aumentar a dificuldade da realização destes ataques, a capacidade net_raw deve ser sempre removida aquando do lançamento de um contentor. No entanto, e embora eficaz, esta configuração é insuficiente para garantir a segurança total de um contentor.

Duas configurações são sugeridas para aumentar o isolamento a nível da rede, que podem ser empregues singularmente ou em conjunto. A primeira envolve a configuração da capacidade de comunicação entre contentores, que está disponível por omissão no Docker. Esta configuração pode ser feita adicionando uma linha ao ficheiro de configuração do Docker daemon.

"icc": false

Por omissão, o Docker inicia todos os contentores na mesma rede virtual, bridge. A segunda configuração passa, por isso, em criar uma rede virtual dentro do Docker, e atribuir apenas a essa rede os contentores que, em conjunto, sejam seguros.

$ docker network create example-network

$ docker run -it --rm --network example-network ubuntu bin/bash

Contentores em redes diferentes não conseguem ver nem interagir com outros contentores, o que, efetivamente, os isolada na rede. A configuração ao nível do Docker daemon pode parecer, por isso, redundante. No entanto, não é nada mais que a aplicação de defesa em profundidade, caso aconteça alguma falha ou vulnerabilidade noutro mecanismo de segurança.

5.1.5 Distribuição de Atualizações

A solução mais eficaz para manter a segurança de sistemas, computadores e até contentores continua a ser, e continuará a ser, atualizações. Nunca é demais dar ênfase à importância de manter o sistema hospedeiro e o Docker atualizados. A maioria das vulnerabilidade, e principalmente aquelas que levam a ataques de aumento de privilégios e execução remota de código, são, por norma, rapidamente corrigidas e distribuídas na forma de patches.

5.2 Ferramentas de Análise

Sistemas e implementações baseadas em tecnologias de contentorização não utilizam unica- mente contentores, dependendo também de um conjunto de serviços e infraestruturas de apoio. Nesse sentido, e embora as configurações discutidas nos tópicos anteriores sejam eficazes, não são suficientes para garantir a segurança ao nível da infraestrutura da solução desenvolvida. Estas mesmas configurações são executadas, por norma, de forma não automatizada e, por isso, são suscetíveis de erro humano.

Os contentores são mais que o próprio contentor em si, dependendo também das ima- gens utilizadas e das bibliotecas empregues pelas aplicações neles contidas. Devido à sua complexidade, estes componentes não são fáceis de analisar manualmente, sendo necessário recorrer a ferramentas especializadas e automatizadas. Adicionalmente, devido à sua principal propriedade, não é trivial monitorizar o estado interno de um contentor em execução.

Com isto em mente, serão analisados nos tópicos seguintes um conjunto de padrões,

5.2.1 CSVS

O Container Security Verification Standard (CSVS) [35] foi criado pela comunidade OWASP, num esforço para desenvolver um framework de requisitos funcionais e não funcionas de segurança a ter em conta aquando do desenvolvimento de uma solução baseada em contentores, nomeadamente Docker. É uma lista de requisitos de segurança, ou testes, que podem ser utilizados pelos profissionais de segurança para definir o que é um contentor seguro, e verificar o seu nível de segurança. A análise de riscos é sempre bastante subjetiva, ainda mais quando se tenta generalizar um standard, pelo que é normal que não haja consenso total quanto às regras e verificações incluídas. Existem outras soluções e padrões para efetuar a verificação da segurança de contentores, mas, para além de serem excessivamente extensos, incluem regras redundantes.

Figura 5.4: Exemplo de verificações presentes no CSVS.

No CSVS estão definidos três níveis de segurança, cada um exponencialmente mais restrito que o anterior:

• Nível 1 (L1) – Oportunista: Segurança genérica para todo o tipo de projetos com contentores. Defende adequadamente contra as falhas de segurança mais comuns. • Nível 2 (L2) – Normal: Nível de segurança a ter quando os projetos lidam com infor-

mação sensível, com impacto monetário ou na privacidade dos seus utilizadores (ex: personificação, fraudes de cartão de crédito). Defende adequadamente contra a maioria dos riscos associados a contentores.

• Nível 3 (L3) – Avançado: Quando um contentor lida com informação que exige o nível de segurança mais elevado, como o caso de informação militar ou saúde (registos de doentes). Inclui testes em todos os níveis da infraestrutura de uma solução baseada em contentores, que devem ser devidamente documentados.

A melhor forma de aplicar esta framework é através da sua utilização como base no desen- volvimento de uma checklist específica para o projeto em causa, plataforma ou organização. Ao moldar o CSVS, estamos a concentrar-nos nos requisitos que são mais importantes e que verdadeiramente constituem parte integrante da análise de risco do projeto.

5.2.2 Docker Bench

O Docker Bench é uma ferramenta, ou um script, que verifica o nível de segurança de uma implementação baseada em contentores, com recurso a dezenas de boas práticas de configuração. Estes testes são totalmente automatizados, e foram inspirados no benchmark

criado pelo Center for Internet Security (CIS). Para lançar o benchmark, basta apenas correr o contentor fornecido1 com os seguintes parâmetros de entrada.

$ docker run -it --net host --pid host \

--userns host --cap-add audit_control \

-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \ -v /etc:/etc:ro \ -v /usr/bin/docker-containerd:/usr/bin/docker-containerd:ro \ -v /usr/bin/docker-runc:/usr/bin/docker-runc:ro \ -v /usr/lib/systemd:/usr/lib/systemd:ro \ -v /var/lib:/var/lib:ro \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ --label docker_bench_security docker/docker-bench-security

No final do benchmark, é dada uma nota baseada na quantidade de verificações que passaram ou falharam. De notar que estes testes ultrapassam as configurações feitas aquando do lançamento de um contentor, ou mesmo as alterações feitas ao Docker daemon. Aqui, são tidos em consideração fatores como o robustecimento do hospedeiro e a criação de partições específicas para os contentores. Por exemplo, no caso do lançamento do benchmark num sistema Ubuntu normal, é gerado um aviso sobre a criação de uma partição independente para os contentores (Figura 5.5).

Figura 5.5: Lançamento do Docker Bench.

Este benchmark foi criado através do consenso entre vários especialistas nesta matéria, como especialistas em cibersegurança e consultoria. O objetivo era criar um conjunto de guias para a implementação e configuração segura de soluções baseadas em Docker [8]. Os testes efetuados pelo benchmark são particularmente importantes, porque ajudam a compreender quais as falhas de configuração presentes na infraestrutura onde são executados os contentores. Ao endurecer os hospedeiros, por exemplo, reduz-se a superfície de ataque, e ao configurar volumes temporários e partições específicas para os contentores, mitiga-se os problemas criados por um potencial ataque bem sucedido.

5.2.3 Clair

As imagens utilizadas para lançar contentores podem conter várias aplicações e bibliotecas que, por sua vez, podem estar repletas de vulnerabilidades. A forma como são criadas, através da utilização de camadas, não facilita a sua análise manual, sendo por isso necessário recorrer a ferramentas comerciais. Uma das ferramentas mais conhecidas nesta área é o Clair, desenvolvido pela CoreOS.

Esta ferramenta é uma solução open-source para realizar análise estática de imagens Docker colocadas num repositório. Estas imagens são analisadas contra uma base de dados de vulnerabilidades, populada com entradas de, por exemplo, o NIST NVD. Como ferramenta de análise, é bastante eficaz a detetar vulnerabilidades conhecidas que estão contidas nas bibliotecas e aplicações de uma imagem.

Figura 5.6: Arquitetura do Clair [24].

Apesar de poderosa, esta ferramenta não possui nenhuma interface, estando a interação restrita a pedidos REST. Adicionalmente, a configuração, instalação e utilização da ferramenta envolve uma quantidade elevada de comandos, para não falar na configuração obrigatória de uma base de dados PostgreSQL. Com o objetivo de colmatar a má experiência de utilização, foi criada uma ferramenta de mais alto nível, o Klar2. O Klar coordena a análise de imagens entre o Clair e o repositório Docker, facilitando em muito a interação com o utilizador.

Para reduzir a probabilidade da exploração de vulnerabilidades existentes, o Clair – ou outra solução de análise estática de imagens – é de utilização quase obrigatória em implementações baseadas em contentores. Mas, apesar de tudo, esta ferramenta não é capaz de detetar scripts maliciosos ou backdoors introduzidos intencionalmente na imagem, e que não façam parte de especificações presentes na base de dados de vulnerabilidades. Por isso, a sua utilidade para detetar ataques premeditados a contentores, e em especial em plataformas zero-trust, é reduzida.

5.2.4 Falco

Nem todos os riscos podem ser abordados, sendo necessárias ferramentas que confirmem a eficácia das medidas de segurança aplicadas, e que funcionem como uma última linha de defesa, caso tudo o resto falhe. Até agora, foram aplicados vários mecanismos de segurança para combater ameaças em contentores que tinham como finalidade evitar possíveis vulnerabilidades

e ataques. No entanto, é necessário ter também em conta os cenários em que já foi executado um ataque, sendo pertinente gerar alertas que acionem uma resposta mitigadora, aplicando para isso mecanismos de deteção. Esta deteção não é trivial de se conseguir, dadas as propriedades dos contentores, que não permitem “espreitar” com facilidade para dentro deles.

A solução para este problema é a ferramenta open-source Falco, desenvolvido pela Sysdig. O Falco é um monitor de atividade desenvolvido para detetar atividades anormais dentro de um contentor, realizando para isso a auditoria ao sistema no nível mais baixo possível: ao nível do núcleo. Para além de ameaças internas ao contentor, esta ferramenta permite detetar ataques baseados em vulnerabilidades para os quais não existem ainda atualizações. Com um único conjunto de regras, o Falco permite monitorizar contentores, aplicações, o hospedeiro e a rede num único lugar. Para começar a utilizar esta ferramenta, primeiro é necessário descarregar e instalar o Sysdig, seguido do Falco.

apt-get install sysdig falco

Concluída a configuração, pode iniciar-se o Falco e começar a monitorizar contentores. Para demonstrar as capacidades desta ferramenta, lança-se um contentor e cria-se uma bind shell para o mesmo. Na consola onde está a correr o Falco, é possível observar o aviso que foi lançado quando detetada a ligação shell (Figura 5.7).

Figura 5.7: Monitorização de um contentor com recurso ao Falco.

5.3 Verificações de Segurança

Para comprovar a eficácia das configurações e ferramentas analisadas nos tópicos anteriores, serão agora executados os mesmos ataques a contentores empregues no capítulo anterior. O processo de demonstração será o mesmo, apenas com a diferença do nível de segurança. Os contentores serão configurados da forma mais segura possível, tendo em conta o cenário apresentado, e os resultados monitorizados com recurso ao Falco. O Clair não é utilizado porque os ataques demonstrados não fazem uso de vulnerabilidades em aplicações, mas sim de scripts maliciosos que escapam às verificações.

O robustecimento de hospedeiros e servidores não só é obrigatório em todas as implemen- tações, como também escapa ao âmbito desta dissertação, pelo que não foi feito uso do Docker Bench e do CSVS. Adicionalmente, ambas as ferramentas são específicas às arquiteturas dos sistemas onde são utilizadas, pelo que também não faz sentido aplicar neste cenário.

Os contentores serão lançados com todas as configurações dos tópicos anteriores, nomea- damente user namespaces, remoção de todas as capacidades, limitação de recursos e I/O, e encobrimento a nível da rede. Segue-se um exemplo de um contentor configurado.

$ docker network create test-network

$ docker run -it --rm --read-only \

--network test-network --cap-drop=all \ --security-opt no-new-privileges --cpus="0.1" \ -m 100mb --ulimit nproc=10 --device-read-bps \ /dev/sda:50mb --name ubuntu ubuntu /bin/bash

Relativamente à ultima demonstração, esta continuará a fazer uso de uma versão intenci- onalmente desatualizada do Docker. O objetivo aqui não é demonstrar a exploração da vulnerabilidade, mas mostrar o que pode acontecer no caso de um ataque de dia zero, em que se recorre uma vulnerabilidade desconhecida.

5.3.1 Distúrbio de Serviços

Esta demonstração será idêntica à demonstração realizada na Secção 4.4.1. Será lançada uma

Fork Bomb e serão observados os resultados em relação aos restantes contentores e hospedeiro.

É de esperar que, dadas as configurações efetuadas, o ataque seja evitado. O primeiro passo é iniciar o Falco e começar a observar o contentor malicioso.

$ falco container.name = forkbomb

De seguida, lança-se o contentor malicioso com todas as configuração definidas anteriormente. É lançado também um contentor normal para testar os efeitos do ataque, se chegar a esse ponto.

$ docker network create test-network

$ docker run -it --rm --read-only \

--network test-network --cap-drop=all \ --security-opt no-new-privileges --cpus="0.1" \ -m 100mb --ulimit nproc=10 --device-read-bps \ /dev/sda:50mb --name forkbomb forkbomb /bin/bash

Os resultados são os esperados, no sentido em que o ataque de distúrbio de serviços não tem sucesso. É possível observar os erros lançados durante a execução do contentor, relativos à limitação da velocidade de leitura (Figura 5.8). No entanto, o Falco não detetou nenhum acontecimento fora do normal. Isto acontece porque a Fork Bomb utilizada não faz uso de nenhuma syscall especial, não passando de um script em bash vulgar.

Figura 5.8: Demonstração de um ataque de exaustão de recursos.

Efetivamente, a configuração que está a ter efeito aqui é a limitação ao nível de I/O, mas qualquer uma das restrições de recursos funcionaria. No caso de uma restrição a nível de memória, assim que é ultrapassado o limite imposto, o contentor é terminado (Figura 5.9).

Figura 5.9: Demonstração de uma restrição de memória.

5.3.2 ARP Spoofing

Esta demonstração segue as mesmas linhas da anterior, em que será espelhado o ataque da Secção 4.4.2, mas agora com as devidas configurações de segurança. Serão aplicadas ambas as medidas de isolamento da rede discutidas, tanto a nível do daemon, como do contentor.

$ docker create network test-network1 $ docker create network test-network2

$ docker run -it --rm --network test-network1 ubuntu /bin/bash $ docker run -it --rm --network test-network2 ubuntu /bin/bash

Os contentores são criados em redes separadas, o que reduz a visibilidade que têm, impossibi- litando assim qualquer tipo de comunicação. É possível observar na figura 5.10 que ambos os contentores têm endereços IP pertencentes a redes diferentes.

Adicionalmente, qualquer tipo de encaminhamento de pacotes entre redes e contentores é desativado pelas configurações do daemon. Juntamente com a remoção da capacidade de criação arbitrária de pacotes net_raw, esta configuração é, em teoria, suficiente. Mesmo quando colocados na mesma rede, os contentores não conseguem comunicar (Figura 5.11).

Figura 5.11: Ping entre dois contentores.

Na realidade, ao desativar a comunicação entre contentores, o Docker está a criar uma entrada na firewall do hospedeiro que impede o encaminhando de todo o tipo de pacotes, a não ser que explicitamente permitido (Figura 5.12). Estas configurações são eficazes a esconder a rede onde os contentores estão inseridos e a prevenir qualquer tipo de ataque a partir da rede.

Figura 5.12: Entradas na firewall to hospedeiro.

5.3.3 Reverse Shell

O lançamento de uma reverse shell não implica a comunicação entre contentores ou a utilização de chamadas e capacidades não permitidas. Por isso, o lançamento de uma reverse shell poderá ser o ataque que irá fugir à norma, não sendo possível evitar com recurso às configurações efetuadas. Mesmo assim, será feita a demonstração usando todas as configurações no daemon e contentor. À semelhança do anterior, o Falco estará a correr nos bastidores.

Como é de esperar, o ataque é bem sucedido, não sendo nenhuma das configurações eficaz na resolução do problema. No entanto, o Falco consegue detetar o lançamento da shell remota e lança um aviso (Figura 5.14).

Figura 5.14: Deteção de uma reverse shell.

Pode concluir-se que este tipo de ataque não é possível de prevenir somente com recurso a configurações no Docker e contentores. Para conseguir evitar o lançamento de uma reverse

shell é necessário recorrer a configurações no hospedeiro que impeçam a saída de pacotes do

ambiente seguro definido. Quando isto não é possível, ou quando é necessário que o contentor esteja ligado à rede externa, é pertinente implementar ferramentas de deteção adequadas, como o Falco. Adicionalmente, deve ser implementado um mecanismo de resposta que termine a ligação ou o contentor alvo do ataque.

5.3.4 Aumento de Privilégios

A vulnerabilidade que é explorada nesta demonstração necessita, para elevar privilégios, que o PID dentro e fora do contentor sejam iguais. A configuração de user namespaces resolve o problema ao criar um utilizador adicional, com um PID diferente, e atribuir a este os contentores. Aliás, uma das recomendações sugeridas aquando da descoberta desta vulnerabilidade foi configurar precisamente este mecanismo de segurança. Para testar a eficácia dos user namespaces configurados no Docker daemon, o contentor é lançado sem configurações de segurança.

Como é de esperar, o ataque falha quando tenta escrever no sistema de ficheiros e não tem permissão. No Falco, é possível observar o aviso de severidade crítica, quando são detetadas as syscall que foram lançadas ao fazer o pooling para encontrar o PID (Figura 5.15). Não só é possível de impedir o ataque, como também de detetar e reagir em conformidade.

Adicionalmente, este ataque em específico pode ser evitado com recurso a configurações do contentor, caso não se consiga implementar user namespaces. Como o ataque necessita de permissões de escrita, a simples configuração do sistema de ficheiros como só de leitura soluciona o problema.

Figura 5.16: Falha na elevação de privilégios.

5.4 Contentores Virtualizados

Enquanto que eficazes a evitar a grande maioria dos ataques, as configurações realizadas nos tópicos anteriores não são suficientes para impedir ataques mais sofisticados. O cenário é ainda pior se for considerado um ambiente de confiança zero, em que não se pode confiar nem nas imagens dos contentores, nem nas aplicações que executam dentro deles. A aplicação do modelo de confiança zero, juntamente com a virtualização de contentores, é a única forma de conseguir

No documento Execução segura com contentores (páginas 59-71)

Documentos relacionados