Desenvolvimento

22 jun, 2017

Protegendo um Raspberry Pi incorporado em seu dispositivo IoT

Publicidade

Os dispositivos para a Internet de Coisas (IoT) são frequentemente expostos a informações sensíveis, como se você está em casa, ou então eles têm controle de coisas importantes, como se o seu bebê está ouvindo músicas calmantes ou uma sirene estridente quando seu bebê se agita às 3 horas da manhã . Os desenvolvedores devem aprender a como proteger os dispositivos IoT contra intrusões.

O Raspberry Pi é, em muitos aspectos, um excelente sistema para usar como núcleo de processamento de dispositivos IoT. Ele executa um sistema operacional de propósito geral (geralmente Linux, embora exista uma versão do Windows). Como desenvolvedor, você possui acesso completo a todos os serviços e funções que um sistema operacional fornece. No entanto, essa flexibilidade vem acompanhada de riscos de segurança.

Por padrão, um sistema operacional de propósito geral oferece muitos serviços ou funções potencialmente arriscadas que não são necessários para um dispositivo IoT. Por exemplo, raramente uma campainha inteligente precisa executar um navegador Web, atuar como um servidor FTP ou usar o SSH para se conectar a servidores aleatórios.

Neste artigo, você aprende como identificar exatamente o que o Raspberry Pi precisa fazer enquanto incorporado em um dispositivo IoT e, em seguida, como evitar que o Pi faça qualquer outra coisa. Para proteger o Pi, executamos um script para identificar o padrão de uso, que define o que ele está fazendo durante um período de tempo. Nós armazenamos essas informações em um arquivo. Mais tarde, executamos outro script que lê esse arquivo e o usa para aplicar o padrão de uso e evitar que outras coisas aconteçam no Pi.

Esta técnica é muito específica para usar em um computador que executa um sistema operacional de propósito geral. No entanto, os dispositivos IoT tendem a ter padrões de uso muito mais rígidos. Então, esta técnica funciona bem para um Raspberry  Pi incorporado em um dispositivo IoT.

No vídeo abaixo, você pode me ver fazendo uma apresentação deste artigo e demonstrando os scripts que discuto aqui:

O que você precisa para criar seu aplicativo

1. Um Raspberry Pi com o Sistema Operacional Raspian instalado. As técnicas e scripts neste artigo foram todos verificados em tal sistema. Eles podem funcionar em outros sistemas, se eles forem baseados no UNIX, mas não é garantido.

2. Conhecimento básico de JavaScript e Node.js. Eu escolhi usar Node.js porque suponho que mais leitores conheçam o JavaScript do que uma das linguagens de script tradicionais, como o Python.

3. Pacotes Node.js:

  • npm (para instalar outros pacotes)
  • ps-man (para obter a lista de pacotes)
  • node-netstat (para obter a lista de sockets abertas)

Conhecimento básico da administração do sistema Linux. Identificar um padrão de uso e aplicá-lo são tarefas de administração do sistema. Você precisa ter alguma experiência com a execução desses comandos de administração do sistema Linux: ps, kill, netstat e tcpdump.

Pegue o código utilizado no meu repositório no GitHub.

Configurando seu Raspberry Pi

A partir de uma instalação limpa do Raspian, que é o sistema operacional Raspberry Pi padrão que você pode fazer o download no site do Raspberry Pi, execute esses comandos no Raspberry Pi para instalar os pacotes Node.js necessários e algumas outras ferramentas que não estão instaladas por padrão.

sudo apt-get install tcpdump
sudo apt-get install npm
npm install ps-man
npm install node-netstat

Identificando padrões de uso

O padrão de uso que seguiremos neste artigo consiste em dois tipos de dados:

Processos

Os processos identificam o que o Pi faz.

Conexões de rede

As conexões de rede identificam como o Pi se comunica com todas as outras coisas. As conexões podem ser ainda divididas em dois tipos:

  • Sockets de escuta. As sockets de escuta são usadas por outros sistemas para se conectar ao Pi como um servidor.
  • Conexões ativas. As conexões ativas podem permitir que o Pi atue como um cliente conectando-se a outros dispositivos como servidores.

Processos e sockets de escuta podem ser identificados através da pesquisa periódica do Pi para ver o que está sendo executado atualmente e quais portas estão no modo de escuta, aguardando conexões.

Conexões ativas, no entanto, podem ser de vida muito curta. Para identificá-las, você precisa ter um processo tcpdump ativo.

Você pode usar o script get_pattern.js que está disponível no meu repositório GitHub (Securing_Raspberry_Pi_in_Your_Device) para identificar o padrão de uso. Você pode colocar esse script em qualquer lugar no sistema de arquivos do seu Raspberry Pi e executar o script usando Node.js:

node get_patterns.js

Processos

Para construir a parte de processos padrão de uso, que representa o que o Pi faz, precisamos examinar repetidamente a lista de processos e ver o que está sendo executado.

Você pode usar o pacote ps-man para obter a lista de processos, que é semelhante ao comando ps. Você pode usar esse código para ligar para o pacote ps-man para obter uma lista de todos os comandos do processo:

var ps = require("ps-man");
 
ps.list({}, function(err, result) {
    for(var i=0; i<result.length; i++)
        console.log(results[i].command);
};

Este código mostra um instantâneo dos comandos atuais. Para fundir os instantâneos de diferentes pontos no tempo, usamos uma tabela de hash com o nome do comando como chave. Por exemplo, você pode usar esse código para criar uma tabela de hash e atualizá-la a cada segundo:

var processHistory = {};
 
var getProcesses = function() {
    ps.list({}, function(err, result) {
        for(var i=0; i<result.length; i++)
            processHistory[result[i].command] = true;
        });
};
 
setInterval(getProcesses, 1000);

Sockets de escuta

Quando o Raspberry Pi é um servidor, ele escuta em uma porta até que alguma outra entidade se conecte a ela. Você pode usar o seguinte comando netstat para obter apenas as sockets que estão aguardando conexões:

netstat({filter: {state: "LISTEN"} }, function(data) { … });

Este filtro só captura TCP e TCP para sockets IPv6. As sockets UDP não têm um estado de escuta e o node-netstat não os retorna de qualquer maneira. Mais adiante neste artigo, descreverei como você pode trabalhar com sockets UDP.

A função de callback é chamada com cada socket que corresponde ao filtro e obtém as informações de socket em uma estrutura. Uma socket de escuta pode ser identificada por dois recursos: o protocolo (TCP, UDP ou as versões IPv6 desses protocolos) e o número da porta. A seguinte função de callback ,obtém esses dados e os armazena em uma tabela de hash semelhante àquela usada nos processos anteriormente.

function(data) {
    listenSockets[data["local"]["port"] + "/" + data["protocol"]] = true;
}

Construindo um padrão de uso de polling periódico

Para pesquisar processos e sockets de rede que estão ouvindo conexões, você precisa determinar dois parâmetros:

  • Frequência de polling. Com que frequência devemos analisar os processos em execução e as sockets de rede?
  • Duração do ciclo. Quanto tempo o programa deve rastrear o dispositivo para obter uma imagem completa? Por exemplo, se o dispositivo carregar para um servidor uma vez por hora, esse período precisa ser de pelo menos uma hora. Se o programa entrar em contato com um servidor para procurar por atualizações uma vez por dia, esse período precisa ser de pelo menos um dia. Durante esse período de tempo, você precisa fornecer ao dispositivo todas as condições de entrada que possa ter na vida real para que suas consequências façam parte do padrão de uso detectado.

O código a seguir mostra a definição desses parâmetros:

// Polling frequency, in seconds. This is how often we look at the
// process and socket lists.
var pollingFreq = 1;
 
// Time for a full cycle of the device, in seconds. This means that
// anything that happens on this device, we expect to happen at least
// once during that time.
var cycleLength = 60;

Para iniciar o polling, usamos o comando setInterval. O valor de retorno desse comando é um identificador que pode ser usado para depois interromper o polling.

// Set up the polling
var processInterval = setInterval(getProcesses, pollingFreq *1000);
var socketInterval = setInterval(getSockets, pollingFreq *1000);
 
 
var pollingDone = false;
 
// End polling
var endPolling = function() {
    clearInterval(processInterval);
    clearInterval(socketInterval);
 
    pollingDone = true;
};

Finalmente, podemos usar o comando setTimeout para finalizar o polling. Se quisermos, também pode relatar os resultados:

// Set up end of polling
setTimeout(function() {
        endPolling();
        console.log(JSON.stringify({
            processes: processHistory,
            sockets: listenSockets      
        }));
    }, cycleLength*1000);

Conexões ativas

Encontrar informações sobre conexões onde o dispositivo é o cliente é mais difícil. Essas conexões podem ser de vida muito curta, então o realizar pesquisas periodicamente não irá encontrá-las. A solução neste caso é executar o comando tcpdump, que é um sniffer de rede que vê tudo o que passa pela interface. (Você pode ler mais sobre este comando tcpdump na sua página man.)

Neste artigo, estamos apenas interessados no primeiro pacote da conexão (que é sempre cliente para servidor), e somente se esse primeiro pacote for enviado, do nosso dispositivo IoT para a rede.

No caso dos pacotes TCP, o primeiro pacote da conexão é o único a ter o sinalizador SYN sem um sinalizador ACK. (Você pode ler sobre a iniciação da conexão TCP na Wikipedia).

O comando a seguir (que deve ser digitado todo em uma linha) mostra uma linha para cada conexão TCP onde o dispositivo IoT é um cliente e mantém uma cópia da linha em um arquivo chamado tcpAsClient:

sudo tcpdump --direction=out -n 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0 and tcp' | tee tcpAsClient

Execute este comando na linha de comando do Pi e, em seguida, abra um browser no Pi e navegue para ver o que você está acessando. Por causa do comando tee, o resultado de tcpdump é tanto para a janela da linha de comando quanto para o arquivo tcpAsClient.

O resultado no tcpAsClient inclui o endereço IP e o número da porta do destino. No entanto, os endereços IP são menos úteis do que você pensa porque os dispositivos tipicamente tentam acessar nomes de host, não endereços IP. Os grandes sites, como www.ibm.com, geralmente têm vários endereços IP, que você pode ver ao resolver o nome do host da linha de comando, usando o comando ping www.ibm.com ou usando as ferramentas do Google — os endereços de IP provavelmente serão diferentes.

Para tornar as coisas mais complicadas, a pesquisa de DNS reversa geralmente não resolve para esse nome de host. Para resolver este problema, podemos executar um comando tcpdump separado para capturar as requisições de DNS e suas respostas:

sudo tcpdump -n 'port 53' | tee dns

O script parseTcp.js que está disponível no meu repositório no GitHub ilustra como get_pattern.js analisa o resultado dos comandos tcpdump e depois os combina. Eu vou explicar apenas as partes mais interessantes deste script.

A maneira mais fácil de ler um arquivo é usando a função fs.readFile(). O fragmento abaixo lê um arquivo (neste caso, o nome do arquivo é “dns”) e, em seguida, chama a função de retorno de chamada. O conteúdo do arquivo está em um buffer, no parâmetro data.

fs = require("fs")
fs.readFile("dns", function(err, data) {…});

Primeiro, você cria uma série de linhas.

var lines = data.toString().split("\n");

Os campos no resultado de tcpdump são separados por espaços. No caso do DNS, o sexto campo ([5] em uma array porque as arrays são baseadas em zero) é um identificador. No caso de uma requisição, um sinal de mais é anexado ao número. No caso de uma resposta, o oitavo campo é o tipo de resposta seguido por um valor, depois outro tipo de resposta e um valor, e assim por diante. Os endereços IP têm um tipo A e geralmente aparecem por conta própria. O código a seguir é um exemplo de uma requisição e sua resposta:

19:09:34.158059 IP 172.16.1.1.45897 > 172.16.0.1.53: 35156+ A? d29usylhdk1xyu.cloudfront.net. (47)
 
19:09:34.214358 IP 172.16.0.1.53 > 172.16.1.1.45897: 35156 8/0/0 A 52.85.202.13, A 52.85.202.254, A 52.85.202.106, A 52.85.202.237, A 52.85.202.137, A 52.85.202.204, A 52.85.202.68, A 52.85.202.248 (175)

As requisições são rastreadas em uma tabela de hash, com o identificador como uma chave. As entradas de DNS retornadas são rastreadas em outra tabela de hash, com o endereço IP como a chave e o nome do host como o valor.

Analisar o arquivo tcpAsClient é mais fácil porque estamos interessados apenas no endereço IP (para obter o nome do host) e no número da porta no lado remoto, que é sempre o destino nos pacotes que rastreamos. Esta informação está na quinta entrada na linha, os 4 bytes do endereço IP seguido de um ponto e, em seguida, o número da porta:

9:13:16.961446 IP 172.16.1.1.49494 > 80.70.128.24.80: Flags [S], seq 3551986630, win 29200, options [mss 1460,sackOK,TS val 69478387 ecr 0,nop,wscale 7], length 0

O resultado é uma tabela de hash com números de porta e os hosts aos quais o dispositivo se conectou naquela porta. O resultado é organizado de forma a facilitar a utilização das informações para produzir regras de firewall.

No caso do UDP, que não possui conexões, é difícil distinguir entre requisições e respostas. No entanto, podemos obter todos os pacotes UDP de saída executando este comando:

sudo tcpdump --direction=out -n udp | tee udpPackets

Analisar esse arquivo é semelhante a analisar o arquivo TCP.

Adicionando o comando tcpdump ao script get_pattern.js

Embora você possa executar o comando tcpdump manualmente como fizemos na seção anterior, é mais fácil ter o Node executando tcpdump antes que ele comece a pesquisar por informações. Enquanto o Node está pesquisando informações de processo e de socket, tcpdump pode executar processos separados para reunir as informações de conexão ativa. Depois, após um ciclo completo do dispositivo ter passado, Node pode parar os processos tcpdump, analisar os resultados e salvá-los em um arquivo para uso futuro.

A biblioteca child_process do Node inicia processos e se comunica com eles. Em geral, é assim que você executa um processo:

var child_process = require("child_process");
var process = child_process.spawn("cp", ["/etc/passwd", "."]);

Nesse caso, o comando que chamamos é sudo porque tcpdump deve ser executado com privilégios de root.

O comando tcpdump é um parâmetro no comando sudo, assim como seus parâmetros. Os parâmetros devem ser strings separados na lista. Normalmente, o shell cuida disso, mas esse mecanismo ignora o shell.

O parâmetro de direção é um pouco complicado porque, para dois dos comandos tcpdump, só queremos ver os pacotes saírem. Mas, para o comando tcpdump para DNS, queremos ver todos os pacotes, independentemente da direção.

var process = child_process.spawn("sudo",
    ["tcpdump", "--direction" + (outOnly ? "out" : "inout"), "-n", "-l", filter]);

O processo possui um método on que registra os handlers de eventos, assim como fazem os fluxos de resultado (stdout e stderr). Usamos esses métodos para obter o resultado do tcpdump e enviar todos os erros de volta para o usuário.

var output = "";
process.stdout.on("data", function(data) {
    output += data;
});
 
// Show stderr
process.stderr.on("data", function(data) {
    console.log("stderr on tcpdump:" + data);
});
 
// Display errors
process.on("error",function(err) {
    console.log("Error:" + err);
});

Finalmente, em algum momento, precisamos matar o processo tcpdump. No entanto, não podemos enviar para ele um sinal como enviaríamos um processo normal que tivéssemos gerado. Como esse processo está sendo executado como root, ele só pode ser morto como root. O espaço é acrescentado ao resultado porque, de outra forma, se tcpdump não detectou nenhum pacote, o programa assumirá que ele ainda não terminou e esperará indefinidamente.

setTimeout(function() {
    child_process.spawn("sudo", ["kill", process.pid]);
    callback(output + " ");
}, time);

Precisamos matar o processo tcpdump três vezes, então nós fazemos um wrap desse código em uma função.

var startTcpdump = function(filter, time, callback, outOnly) {
…
};

No script get_pattern.js que está disponível no meu repositório GitHub, a partir da linha 220, você pode ver três chamadas para startTcpdump com um filtro e as chamadas colocam o resultado em uma variável.

Obtendo o arquivo de padrão de uso

O arquivo de padrão de uso só pode ser produzido após o término do polling (caso contrário, ignoramos tudo o que acontece após a sua produção), os três processos tcpdump terminam e os resultados do tcpdump são analisados.

Para garantir a timeline correta, get_pattern.js usa a função WhenDo. Esta função leva dois parâmetros de função. O primeiro é uma condição. O segundo é o retorno de chamada a ser chamado quando a condição se tornar verdadeira. Se a condição for falsa, a função aguarda alguns segundos e tenta novamente (usando setTimeout).

var whenDo = function(when, todo) {
    if (when())
        todo();
    else
        setTimeout(function() {whenDo(when, todo);}, 5000);
};

A primeira vez que esta função é chamada em parseDumps, onde garante que o resultado só será analisado uma vez que esteja disponível:

var parseDumps = function() {
    whenDo(function() {
            return dnsString != "" &&
                tcpAsClient != "" &&
                udpPackets != "";
        },
            function() {
            parseDNS(dnsString);
            parsePorts(tcpAsClient, tcpClients);
            parsePorts(udpPackets, udpClients);
 
            parsingDone = true;
        }
    );
};

A outra é em saveResults, onde ela apenas escreve os resultados em um arquivo (behaviorPattern.json) depois que o polling e a análise estão ambos concluídos:

var saveResults = function()    {
    whenDo(function() {
        return pollingDone && parsingDone;
    }, function() {
        var result = {
            processes: processHistory,
            listen: listenSockets,
            tcp: tcpClients,
            udp: udpClients
        };
        fs.writeFile("behaviorPattern.json",
            JSON.stringify(result) + "\n");
    });
};

Essas duas funções só são chamadas depois de um ciclo completo. No entanto, não sabemos em que ordem elas seriam chamadas ou se os processos do tcpdump serão encerrados antes que os resultados sejam analisados. Então, whenDo garante que tudo funcionará na ordem correta.

Aplicando padrões de uso

Definir o padrão de uso sem realmente aplicá-lo é um exercício de futilidade. No caso dos processos, a maneira mais fácil de aplicar o padrão de uso é matar os processos se eles não devessem estar sendo executados. Todo o resto nos padrões de uso que detectamos atravessa a rede. Isso significa que isso pode ser aplicado através de regras de firewall.

Dois programas no meu repositório GitHub aplicam o padrão de uso. O primeiro programa é enforce_pattern.js, que aplica diretamente a parte do padrão de uso que pode ser aplicada por pollings periódicos. Ele mata qualquer processo que não esteja no padrão de uso estabelecido. O segundo programa é firewall_rules.js, que cria as regras de firewall que impedem os acessos de rede que se desviam do padrão de uso.

Matando processos desconhecidos

O programa enforce_pattern.js lê o arquivo behaviorPattern.json, que é o resultado do programa get_pattern.js. Em seguida, ele executa a função emforceProcesses dez vezes por segundo. Quando você escolhe a frequência para executar enforceProcesses em seu sistema, você precisa considerar as compensações entre dois fatores:

  1. O dano que um processo pode fazer em um curto período de tempo
  2. Os requisitos de desempenho do seu dispositivo

A função enforceProcesses itera sobre todos os processos, da mesma forma que o getProcesses faz. No entanto, ao invés de registrar os processos, ela procura ver se o processo está no padrão de uso existente. Caso contrário, ela emite um comando sudo kill -9 para matar esse processo. No entanto, essas exceções são verificadas pela função allowed:

  • Não faça killer. Enquanto estávamos recebendo o comportamento, o node estava em execução, mas estava sendo executado com um parâmetro diferente, get_pattern.js. Os processos que o ps-man nos informa incluem os parâmetros da linha de comando. Então, node enforce_pattern.js é considerado um processo diferente do node get_pattern.js.
  • Não mate processos cuja timemline contenha sudo kill -9. Esses processos são mais prováveis de serem gerados pelo nosso próprio processo.
  • Não mate os processos trabalhadores do kernel. Ou melhor, não tente matá-los. Fazendo parte do kernel, eles não respondem a nenhum sinal que os torne indestrutíveis.

Criando regras de firewall

A maior parte do padrão de comportamento no arquivo behaviorPattern.json é dedicado ao tráfego de rede. Para restringir o tráfego de rede, o programa firewall_rules.js no meu repositório GitHub cria um script (fwCommands.sh) que especifica as regras do firewall. Você executa este programa uma vez e, em seguida, executa também o fwCommands.sh manualmente (como raiz) ou o adiciona ao script de inicialização, conforme explicado abaixo.

Embora o JavaScript nesses scripts até agora seja bastante simples, talvez você não esteja familiarizado com o firewall do Linux, o iptables. Vou explicar o arquivo fwCommands.sh do meu sistema para mostrar o que ele faz e como controlá-lo.

A primeira parte do fwCommands.sh especifica as regras gerais. Primeiro, eliminamos as políticas de entrada e saída existentes. A terceira cadeia, para a frente, é usada quando o dispositivo encaminha pacotes de uma interface para outra. Mas para este caso de uso, é seguro ignorá-la. É improvável que as pessoas usem Raspberry Pi como roteador de rede.

#! /bin/bash
#
iptables -F INPUT
iptables -F OUTPUT

Essas duas linhas definem a ação padrão para aceitar (ou seja, o pacote é transmitido). Normalmente, para os firewalls, é melhor proibir o que não esteja permitido explicitamente, mas aqui estamos apenas protegendo o tráfego TCP, então qualquer coisa além precisa ser permitida.

iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT

A interface de loopback é usada para comunicação entre diferentes processos no mesmo dispositivo. Portanto, é considerada seguro. Se algum pacote estiver passando por isso, ele pode ser transmitido.

iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

Os pacotes que pertencem a conexões estabelecidas ou a uma conexão relacionada (por exemplo, o canal de dados FTP de um canal de controle FTP estabelecido) também são permitidos.

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

A segunda parte do resultado deriva da lista de portas locais que aceitam conexões. Ele permite a entrada para essas portas de qualquer lugar porque não sabemos se os locais de clientes são ilegítimos. Neste caso, apenas aceitamos a entrada para portas 80 (HTTP) e 22 (SSH).

iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT

Este código é a última parte que lida com a política de entrada. Então, adicionamos uma regra para rejeitar quaisquer outros pacotes TCP (aqueles que não fazem parte de uma conexão estabelecida ou relacionada, e que não estejam indo para portas que permitimos explicitamente). Então, adicionamos uma regra para aceitar qualquer outra coisa.

iptables -A INPUT -p tcp -j REJECT
iptables -A INPUT -j ACCEPT

A terceira e última parte do arquivo deriva do tcpdump que visualiza conexões TCP. Ela permite novas conexões de saída, mas apenas para uma porta e host que aparecem no padrão de comportamento. Neste exemplo, estamos tomando decisões de segurança com base na resolução de DNS, que pode ser falsificada de várias maneiras. Como de costume, há uma compensação entre segurança e usabilidade. Um dispositivo IoT mais sensível pode ter uma política que está baseada não no comportamento observado, mas no design real do dispositivo e no que ele precisa permitir.

iptables -A OUTPUT -p tcp -m state --state NEW -m tcp --dport 80 -d <a href="http://www.ibm.com/"><code>www.ibm.com</code></a> -j ACCEPT
iptables -A OUTPUT -p tcp -m state --state NEW -m tcp --dport 80 -d <a href="http://www.ibm.com/"><code>www.google.com</code></a> -j ACCEPT
iptables -A OUTPUT -p tcp -m state --state NEW -m tcp --dport 22 -d <a href="http://www.ibm.com/"><code>1</code></a>0.20.30.40 -j ACCEPT

Tal como acontece com a política de entrada, qualquer outro pacote TCP é descartado ou rejeitado e qualquer pacote não-TCP é aceito.

iptables -A OUTPUT -p tcp -j DROP
iptables -A OUTPUT -j ACCEPT

Do protótipo à produção

Este artigo apresentou alguns scripts que você pode usar em um protótipo de uma solução IoT que usa um Raspberry Pi incorporado em um dispositivo IoT. Obviamente, alguns recursos foram omitidos por design. A lista a seguir inclui alguns dos recursos ausentes e algumas sugestões sobre como implementá-los.

Adicione um backdoor

Às vezes, é útil administrar remotamente um dispositivo. Mas se você restringir a segurança, talvez não seja mais capaz de usar ssh. Ou talvez você possa acessar o dispositivo, mas sempre que você tentar executar um comando, ele será morto porque não está no padrão de uso.

A solução tradicional para este problema é deixar um backdoor, ou um caminho para o dispositivo. No entanto, isso pode ser perigoso porque permite acesso a qualquer um que possa descobrir sobre isso. Uma solução é ter o backdoor autenticado por você usando um mecanismo seguro, como senhas de utilização única. Para torná-lo ainda mais forte, você pode usar senhas de utilização única que não estejam armazenadas no dispositivo, usando o padrão S/Key, por exemplo.

Inicie a proteção automaticamente

Você obviamente precisa da proteção para iniciar automaticamente após uma reinicialização, executando tanto o node enforce_protection.js quanto  fwCommands.sh. O script para modificar é /etc/rc.local.

No entanto, existe um problema potencial. Alguns processos são executados apenas durante a inicialização (fwCommands.sh, para identificar um exemplo óbvio). Para resolver este problema, você pode ter o script de inicialização executando o comando get_pattern.js para obter essa parte do padrão de uso.

Verifique a identidade dos arquivos executáveis

Agora, o arquivo behaviorPattern.json identifica comandos com o nome que foi usado para executá-los, que pode ser um caminho absoluto ou um arquivo executável que é encontrado no caminho. Infelizmente, se um invasor conseguir substituir um arquivo executável em nosso programa, ele não poderá pará-lo.

A solução é identificar os arquivos executáveis com uma soma de verificação criptográfica, usando o pacote cripto-js, por exemplo. (Você pode ler sobre este pacote no site npm.) Esta solução pode identificar casos em que o nome do arquivo é o mesmo, mas o arquivo em si muda.

Apenas permita killers de processos que nosso processo gera

Agora, qualquer processo de sudo kill -9 está protegido das regras no programa enforce_pattern.js. Esta regra é excessivamente tolerante. Criamos nossos próprios processos kill e podemos obter PIDs disso. Em vez de permitir todos os processos que se parecem com eles, podemos excluir aqueles que criamos.

Adicione suporte UDP e IPv6.

Atualmente, as regras de firewall apenas permitem todo o tráfego UDP e IPv6. O IPv6 não é um problema porque está desativado por padrão. Mas permitir UDP requer algum trabalho porque o UDP é sem conexão, o que significa que as portas do servidor não estão em um estado de escuta.

Para obter tais portas, é necessário alterar o filtro para um que procura por portas sem um endereço estrangeiro. Também é necessário alterar o comando padrão que node-netstat causa porque o padrão apenas retorna TCP. Então, você pode alterar o script firewall_rules.js para fornecer os comandos corretos para filtrar UDP.

Conclusão

Como um Raspberry Pi executa um sistema operacional de propósito geral, ele pode deixar-se aberto a problemas de segurança que parecem prejudicar os dispositivos IoT hoje. Ao usar as habilidades de administração do sistema Linux para identificar um padrão de uso, você pode reduzir suas exposições de segurança, restringindo o acesso apenas aos processos e conexões que fazem sentido para o seu dispositivo IoT.

 

***

Ori Pomerantz faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela Redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://www.ibm.com/developerworks/library/iot-security-pi-usage-patterns/index.html