Milhões de binários depois. Como o Linux foi fortalecido

TL; DR . Neste artigo, exploramos esquemas de proteção que funcionam imediatamente em cinco distribuições populares do Linux. Para cada um, pegamos a configuração padrão do kernel, baixamos todos os pacotes e analisamos os esquemas de proteção nos arquivos binários anexados. Consideramos as distribuições do OpenSUSE 12.4, Debian 9, CentOS, RHEL 6.10 e 7, bem como do Ubuntu 14.04, 12.04 e 18.04 LTS.

Os resultados confirmam que mesmo esquemas básicos, como canários de pilha e código independente de posição, ainda não são usados ​​por todos. A situação é ainda pior para os compiladores quando se trata de proteção contra vulnerabilidades, como o confronto de pilhas, que entrou em cena em janeiro após a publicação de informações de vulnerabilidade no systemd . Mas nem tudo é tão desesperador. Em uma parte significativa dos binários, métodos básicos de proteção são implementados e seu número está aumentando de versão para versão.

A verificação mostrou que o maior número de métodos de proteção é implementado no Ubuntu 18.04 no sistema operacional e no nível do aplicativo, seguido pelo Debian 9. Por outro lado, no OpenSUSE 12.4, CentOS 7 e RHEL 7, esquemas básicos de proteção também são implementados e a proteção contra colisões de pilhas é aplicada ainda mais amplamente. com um conjunto de pacotes muito mais denso por padrão.

1. Introdução


É difícil fornecer software de alta qualidade. Apesar do grande número de ferramentas avançadas para análise de código estático e análise dinâmica em tempo de execução, além de progressos significativos no desenvolvimento de compiladores e linguagens de programação, o software moderno ainda sofre de vulnerabilidades que são constantemente exploradas por criminosos cibernéticos. A situação é ainda pior em ecossistemas que incluem código legado. Nesses casos, não somos apenas confrontados com o eterno problema de encontrar possíveis erros explorados, mas também somos limitados por estruturas rígidas de compatibilidade com versões anteriores, que geralmente exigem a manutenção de códigos limitados e, pior ainda, vulneráveis ​​ou com bugs.

É aqui que os métodos de proteção entram em ação. Não podemos evitar alguns tipos de erros, mas podemos dificultar a vida de um invasor e resolver parcialmente o problema, impedindo ou impedindo a operação desses erros. Essa proteção é usada em todos os sistemas operacionais modernos, no entanto, os métodos variam muito em complexidade, eficiência e desempenho: de canários de pilha e ASLR a proteções completas de CFI e ROP . Neste artigo, consideraremos quais métodos de proteção são usados ​​nas distribuições Linux mais populares na configuração padrão e também estudaremos as propriedades dos binários distribuídos pelos sistemas de gerenciamento de pacotes de cada distribuição.

CVE e segurança


Todos nós já vimos artigos com títulos como "Aplicativos Mais Vulneráveis ​​do Ano" ou "Sistemas Operacionais Mais Vulneráveis". Normalmente, eles fornecem estatísticas sobre o número total de registros de vulnerabilidade, como o CVE (Vulnerability and Exposures), obtido do National Vulnerability Database (NVD) do NIST e de outras fontes. Posteriormente, esses aplicativos ou sistemas operacionais são classificados pelo número de CVEs. Infelizmente, embora os CVEs sejam muito úteis para rastrear problemas e informar fornecedores e usuários, eles falam pouco sobre a real segurança do software.

Como exemplo, considere o número total de CVEs nos últimos quatro anos para o kernel Linux e as cinco distribuições de servidores mais populares, como Ubuntu, Debian, Red Hat Enterprise Linux e OpenSUSE.


Fig. 1

O que esse gráfico nos diz? Mais CVE significa que uma distribuição é mais vulnerável que outra? Não há resposta. Por exemplo, neste artigo, você verá que o Debian implementa mecanismos de segurança mais rigorosos em comparação com, digamos, OpenSUSE ou RedHat Linux, e ainda assim o Debian tem mais CVE. No entanto, eles não significam necessariamente segurança enfraquecida: mesmo ter um CVE não indica se a vulnerabilidade é explorável . As pontuações de gravidade dão uma idéia da probabilidade de exploração da vulnerabilidade, mas, em última análise, a capacidade de exploração depende em grande parte da proteção presente nos sistemas afetados, bem como dos recursos e capacidades dos atacantes. Além disso, a falta de relatórios do CVE não diz nada sobre outras vulnerabilidades não registradas ou desconhecidas . A diferença no CVE pode ser explicada não pela qualidade do software, mas por outros fatores, incluindo os recursos alocados para teste ou o tamanho da base de usuários. Em nosso exemplo, mais CVEs Debian podem simplesmente indicar que o Debian está entregando mais pacotes de software.

Obviamente, o sistema CVE fornece informações úteis que permitem criar proteções apropriadas. Quanto melhor entendermos os motivos da falha do programa, mais fácil será identificar possíveis métodos de operação e desenvolver mecanismos apropriados de detecção e resposta . Na fig. A Figura 2 mostra as categorias de vulnerabilidade para todas as distribuições nos últimos quatro anos ( fonte ). É imediatamente óbvio que a maioria dos CVEs se enquadra nas seguintes categorias: negação de serviço (DoS), execução de código, estouro, corrupção de memória, vazamento de informações (exfiltração) e escalonamento de privilégios. Embora muitos CVEs sejam abordados várias vezes em diferentes categorias, em geral, os mesmos problemas persistem de ano para ano. Na próxima parte do artigo, avaliaremos o uso de vários esquemas de proteção para impedir a exploração dessas vulnerabilidades.


Fig. 2

As tarefas


Neste artigo, pretendemos responder às seguintes perguntas:

  • Qual é a segurança de várias distribuições Linux? Quais mecanismos de defesa existem nos aplicativos do kernel e do espaço do usuário?
  • Como a adoção de mecanismos de proteção para várias distribuições mudou ao longo do tempo?
  • Quais são as dependências médias de pacotes e bibliotecas para cada distribuição?
  • Quais proteções são implementadas para cada binário?

Escolhendo distribuições


Acontece que é difícil encontrar estatísticas precisas sobre as instalações de distribuição, pois na maioria dos casos o número de downloads não indica o número de instalações reais. No entanto, as variantes do Unix compõem a maioria dos sistemas de servidores (69,2% nos servidores da Web, de acordo com estatísticas da W3techs e de outras fontes), e sua participação está aumentando constantemente. Assim, em nosso estudo, nos concentramos em distribuições disponíveis imediatamente na plataforma do Google Cloud . Em particular, escolhemos o seguinte sistema operacional:

Distribuição / versãoO núcleoConstruir
OpenSUSE 12.44.12.14-95.3-default# 1 SMP Wed Dec 5 06:00:48 UTC de 2018 (63a8d29)
Debian 9 (estiramento)4.9.0-8-amd64# 1 SMP Debian 4.9.130-2 (27/10/2018)
CentOS 6.102.6.32-754.10.1.el6.x86_64Terça-feira 15 janeiro 17:07:28 UTC 2019
CentOS 73.10.0-957.5.1.el7.x86_64# 1 SMP Fri Feb 1 14:54:57 UTC 2019
Servidor Red Hat Enterprise Linux 6.10 (Santiago)2.6.32-754.9.1.el6.x86_64# 1 SMP Wed 21 de nov 15:08:21 EST 2018
Servidor 7.6 do Red Hat Enterprise Linux (Maipo)3.10.0-957.1.3.el7.x86_64# 1 SMP Thu Nov 15 17:36:42 UTC de 2018
Ubuntu 14.04 (Trusty Tahr)4.4.0–140-genérico
# 166 ~ 04/14/1-Ubuntu SMP Sat Nov 17 01:52:43 UTC 20 ...
Ubuntu 16.04 (Xenial Xerus)4.15.0-1026-gcp# 27 ~ 16.04.1-Ubuntu SMP Sex Dez 7 09:59:47 UTC de 2018
Ubuntu 18.04 (Castor biônico)4.15.0-1026-gcp# 27-Ubuntu SMP Thu Dec 6 18:27:01 UTC de 2018
Quadro 1

Análise


Examinaremos a configuração padrão do kernel, bem como as propriedades dos pacotes disponíveis através do gerenciador de pacotes de cada pacote de distribuição imediatamente. Portanto, consideramos apenas pacotes dos espelhos padrão de cada distribuição, ignorando pacotes de repositórios instáveis ​​(por exemplo, testando espelhos no Debian) e pacotes de terceiros (por exemplo, pacotes Nvidia de espelhos padrão). Além disso, não consideramos compilações personalizadas do kernel ou configurações avançadas de segurança.

Análise de configuração do kernel


Utilizamos um script de análise baseado no verificador kconfig gratuito . Consideramos as opções de proteção prontas para as distribuições nomeadas e as comparamos com a lista do Projeto de Autodefesa do Kernel (KSPP). Para cada parâmetro de configuração, a Tabela 2 descreve a configuração desejada: uma marca de seleção é para distribuições que cumprem as recomendações do KSSP (para obter uma explicação dos termos, veja aqui ; em artigos futuros, descreveremos quantos desses métodos de proteção apareceram e como invadir o sistema na ausência deles).





Em geral, os kernels mais recentes têm configurações mais rigorosas prontas para uso. Por exemplo, o CentOS 6.10 e o RHEL 6.10 no kernel 2.6.32 não possuem a maioria das funções críticas implementadas nos novos kernels, como SMAP , permissões RWX fortes, randomização de endereços ou proteção contra copy2usr. Deve-se notar que muitas das opções de configuração da tabela estão ausentes nas versões mais antigas do kernel e não são aplicáveis ​​na realidade - isso ainda é indicado na tabela como falta de proteção adequada. Da mesma forma, se o parâmetro de configuração não estiver disponível nesta versão e, por segurança, esse parâmetro deverá ser desativado, isso será considerado uma configuração razoável.

Outro ponto ao interpretar os resultados: algumas configurações do kernel que aumentam a superfície de ataque podem ser usadas simultaneamente para segurança. Tais exemplos incluem uprobes e kprobes, módulos de kernel e BPF / eBPF. Nossa recomendação é usar os mecanismos acima para fornecer proteção real, uma vez que eles não são triviais e sua operação pressupõe que atores mal-intencionados já estejam entrincheirados no sistema. Mas se essas opções estiverem ativadas, o administrador do sistema deverá monitorar ativamente o abuso.

Estudando as entradas da Tabela 2, vemos que os kernels modernos oferecem várias opções para proteção contra a exploração de vulnerabilidades, como vazamento de informações e estouro de pilha / pilha. No entanto, percebemos que mesmo as distribuições populares mais recentes ainda não implementaram proteção mais sofisticada (por exemplo, com patches grsecurity ) ou proteção moderna contra ataques de reutilização de código (por exemplo, combinando randomização com esquemas como R ^ X para código ). Pior ainda, mesmo essas defesas mais avançadas não protegem contra uma gama completa de ataques. Portanto, é crucial que os administradores de sistema complementem as configurações inteligentes com soluções que oferecem detecção e prevenção de explorações em tempo de execução.

Análise de aplicação


Não é surpreendente que diferentes distribuições tenham características diferentes de pacotes, opções de compilação, dependências de bibliotecas, etc. Existem diferenças mesmo para distribuições e pacotes relacionados com um pequeno número de dependências (por exemplo, coreutils no Ubuntu ou Debian). Para avaliar as diferenças, baixamos todos os pacotes disponíveis, extraímos seu conteúdo e analisamos os arquivos binários e dependências. Para cada pacote, rastreamos os outros pacotes dos quais depende, e para cada binário, rastreamos suas dependências. Esta seção resume as descobertas.

Distribuições


No total, baixamos 361.556 pacotes para todas as distribuições, extraindo apenas pacotes dos espelhos padrão. Ignoramos pacotes sem arquivos executáveis ​​ELF, como códigos-fonte, fontes, etc. Após a filtragem, restaram 129 569 pacotes, contendo um total de 584 457 arquivos binários. A distribuição de pacotes e arquivos entre distribuições é mostrada na fig. 3)


Fig. 3

Você pode notar que, quanto mais moderna a distribuição, mais pacotes e arquivos binários ela contém, o que é lógico. Ao mesmo tempo, os pacotes Ubuntu e Debian incluem muito mais arquivos binários (módulos e bibliotecas executáveis ​​e dinâmicos) do que o CentOS, SUSE e RHEL, o que potencialmente afeta a superfície de ataque do Ubuntu e Debian (observe que os números refletem todos os binários de todas as versões ou seja, alguns arquivos são analisados ​​várias vezes). Isso é especialmente importante ao considerar as dependências entre pacotes. Portanto, uma vulnerabilidade no binário de um único pacote pode afetar muitas partes do ecossistema, assim como uma biblioteca vulnerável pode afetar todos os arquivos binários que o importam. Como ponto de referência, vejamos a distribuição do número de dependências entre pacotes em vários sistemas operacionais:


Fig. 4

Em quase todas as distribuições, 60% dos pacotes têm pelo menos 10 dependências. Além disso, alguns pacotes têm mais dependências (mais de 100). O mesmo se aplica às dependências inversas de pacotes: como esperado, vários pacotes são usados ​​por muitos outros pacotes na distribuição; portanto, as vulnerabilidades nesses poucos favoritos têm um alto risco. Como exemplo, a tabela a seguir lista os 20 pacotes com o número máximo de dependências inversas no SLES, Centos 7, Debian 9 e Ubuntu 18.04 (cada caixa indica o pacote e o número de dependências inversas).


Quadro 3

Um fato interessante. Embora todos os SOs analisados ​​sejam criados para a arquitetura x86_64 e para a maioria dos pacotes a arquitetura seja definida como x86_64 e x86, os pacotes geralmente contêm binários para outras arquiteturas, como mostra a Fig. 5)


Fig. 5

Na próxima seção, examinaremos as características dos binários analisados.

Estatísticas de proteção binária


Como um mínimo absoluto, você precisa estudar o conjunto básico de opções de proteção para arquivos binários existentes. Várias distribuições Linux vêm com scripts que executam essas verificações. Por exemplo, no Debian / Ubuntu existe um script desse tipo. Aqui está um exemplo de seu trabalho:

$ hardening-check $(which docker) /usr/bin/docker: Position Independent Executable: yes Stack protected: yes Fortify Source functions: no, only unprotected functions found! Read-only relocations: yes Immediate binding: yes 

O script verifica cinco funções de proteção :

  • PIE (Position Independent Executable): indica se a seção de texto do programa pode ser movida na memória para obter a randomização se o ASLR estiver ativado no kernel.
  • Pilha protegida: se os canários da pilha estão incluídos para proteger contra ataques de colisão da pilha.
  • Fonte Fortify: se as funções não seguras (por exemplo, strcpy) são substituídas por suas contrapartes mais seguras e as chamadas verificadas em tempo de execução são substituídas por suas contrapartes não verificadas (por exemplo, memcpy em vez de __memcpy_chk).
  • Realocações somente leitura (RELRO): se as entradas na tabela de movimento são marcadas como somente leitura se funcionaram antes do início da execução.
  • Ligação imediata: se o vinculador de tempo de execução permite todos os movimentos antes de iniciar o programa (isso é equivalente a RELRO completo).

Os mecanismos acima são suficientes? Infelizmente não. Existem maneiras conhecidas de contornar todas as defesas acima, mas quanto mais rigorosa a defesa, maior a barra para o atacante. Por exemplo, as soluções alternativas do RELRO são mais difíceis de aplicar se o PIE e a ligação imediata estiverem em vigor. Da mesma forma, um ASLR completo requer trabalho adicional para criar uma exploração em funcionamento. No entanto, invasores sofisticados estão prontos para enfrentar essas defesas: sua ausência acelerará essencialmente o hacking. Portanto, é imperativo que essas medidas sejam consideradas o mínimo necessário.

Queríamos estudar quantos arquivos binários nas distribuições em questão estão protegidos por eles, além de mais três métodos:

  • Um bit não executável ( NX ) impede a execução em qualquer região que não deve ser executável, como um heap de pilha, etc.
  • RPATH / RUNPATH indica o caminho de execução usado pelo carregador dinâmico para localizar as bibliotecas apropriadas. O primeiro é obrigatório para qualquer sistema moderno: sua ausência permite que os atacantes gravem arbitrariamente a carga útil na memória e a executem como estão. Na segunda, configurações incorretas do caminho de execução ajudam na introdução de código não confiável, o que pode levar a vários problemas (por exemplo, escalação de privilégios , além de outros problemas ).
  • A proteção contra colisão de pilha fornece proteção contra ataques que fazem com que a pilha se sobreponha a outras áreas da memória (como uma pilha). Dadas as explorações recentes que abusam das vulnerabilidades de colisão de heap no systemd , achamos apropriado incluir esse mecanismo em nosso conjunto de dados.

Portanto, sem mais delongas, vamos para os números. As tabelas 4 e 5 contêm uma análise minuciosa de arquivos executáveis ​​e bibliotecas de várias distribuições, respectivamente.

  • Como você pode ver, a proteção do NX é implementada em qualquer lugar, com raras exceções. Em particular, seu menor uso nas distribuições Ubuntu e Debian pode ser observado em comparação com o CentOS, RHEL e OpenSUSE.
  • Canários de pilha não estão disponíveis em muitos lugares, especialmente em distribuições com kernels antigos. Algum progresso foi visto nas recentes distribuições de Centos, RHEL, Debian e Ubuntu.
  • Com exceção do Debian e do Ubuntu 18.04, a maioria das distribuições tem suporte a PIE insuficiente.
  • A proteção contra colisão de pilha é mal implementada no OpenSUSE, Centos 7 e RHEL 7 e está praticamente ausente do resto.
  • Todas as distribuições com kernels modernos têm algum suporte RELRO, com o Ubuntu 18.04 liderando o caminho, com o Debian em segundo lugar.

Como já mencionado, as métricas nesta tabela são médias em todas as versões do arquivo binário. Se você olhar apenas para as versões mais recentes dos arquivos, os números serão diferentes (por exemplo, veja o progresso do Debian com a implementação do PIE ). Além disso, a maioria das distribuições geralmente ao calcular estatísticas verifica a proteção de apenas algumas funções no código binário e, em nossa análise, a porcentagem real de funções reforçadas é indicada. Portanto, se 5 das 50 funções estiverem protegidas no binário, atribuiremos a ele uma classificação de 0,1, que corresponde a 10% das funções reforçadas.


Tabela 4. Características de proteção para arquivos executáveis ​​mostrados na fig. 3 (implementação das funções correspondentes como uma porcentagem do número total de arquivos executáveis)


Tabela 5. Características de proteção para as bibliotecas mostradas na Fig. 3 (implementação das funções correspondentes como uma porcentagem do número total de bibliotecas)

Então, há progresso? Definitivamente: pode ser visto nas estatísticas de distribuições individuais (por exemplo, Debian ), bem como nas tabelas acima. Como um exemplo na fig. A Figura 6 mostra a implementação de mecanismos de defesa em três distribuições consecutivas do Ubuntu LTS 5 (omitimos as estatísticas de proteção contra colisão de pilhas). Percebemos que, de versão para versão, mais e mais arquivos suportam canários empilhados, e também seqüencialmente mais e mais arquivos binários vêm com proteção RELRO completa.


Fig. 6

Infelizmente, vários arquivos executáveis ​​em diferentes distribuições ainda não possuem nenhuma das proteções acima. Por exemplo, olhando o Ubuntu 18.04, você pode ver o ngetty binary (substituição getty), bem como os shells mksh e lksh, o interpretador picolisp, os pacotes nvidia-cuda-toolkit (um pacote popular para aplicativos acelerados por GPU, como estruturas de aprendizado de máquina) e klibc -utils. Da mesma forma, o binário mandos-client (uma ferramenta administrativa que permite reiniciar automaticamente máquinas com sistemas de arquivos criptografados), bem como o rsh-redone-client (reimplementando o rsh e o rlogin) são entregues sem a proteção do NX, embora tenham direitos SUID :(. Além disso, Vários binários suid não têm proteção básica, como canários de pilha (por exemplo, o binário Xorg.wrap do pacote Xorg).

Resumo e considerações finais


Neste artigo, destacamos vários recursos de segurança das distribuições modernas do Linux. A análise mostrou que a distribuição mais recente do Ubuntu LTS (18.04) implementou, em média, a proteção mais forte de sistema operacional e aplicativos entre distribuições com kernels relativamente novos, como Ubuntu 14.04, 12.04 e Debian 9. No entanto, as distribuições CentOS, RHEL e OpenSUSE discutidas em nosso kit por padrão, é emitido um conjunto mais denso de pacotes e as versões mais recentes (CentOS e RHEL) têm uma porcentagem maior de proteção contra colisão de pilhas em comparação com os concorrentes baseados no Debian (Debian e Ubuntu). Comparando as versões do CentOS e RedHat, notamos grandes melhorias na implementação de canários de pilha e RELRO das versões 6 a 7, mas, em média, o CentOS possui mais recursos que o RHEL. Em geral, todas as distribuições devem prestar atenção especial à proteção PIE, que, com exceção do Debian 9 e Ubuntu 18.04, é implementada em menos de 10% dos arquivos binários do nosso conjunto de dados.

Por fim, deve-se notar: embora tenhamos feito a pesquisa manualmente, existem muitas ferramentas de segurança (por exemplo, Lynis , Tiger , Hubble ) que executam análises e ajudam a evitar configurações inseguras. Infelizmente, mesmo uma proteção forte em configurações razoáveis ​​não garante a ausência de explorações. É por isso que estamos firmemente convencidos de que é vital garantir monitoramento e prevenção confiáveis ​​de ataques em tempo real , focando nos modelos operacionais e prevenindo-os.

Source: https://habr.com/ru/post/pt444418/


All Articles