A computação distribuída consiste em tempo e estado. Isto é, para que mais de um componente se comunique, é necessário compartilhar o estado, o que exige tempo.
A maioria dos programadores antropomorfiza seu trabalho. Eles enxergam um thread de controle executando todo o programa da mesma forma como enxergariam a si mesmos fazendo o trabalho inteiro por conta própria. Computadores modernos, entretanto, alternam entre tarefas com muita rapidez e, em sistemas multi-core, multi-CPU ou distribuídos, dois eventos podem ocorrer exatamente ao mesmo tempo. Defeitos rapidamente são postos nas lacunas entre o modelo do programador de como um programa é executado e o que ocorre na realidade. Esses defeitos estão relacionados com interações inesperadas entre threads, processos, tempo e informações. Essas interações ocorrem por meio de estados compartilhados: semáforos, variáveis, o sistema de arquivos e, basicamente, todas as coisas capazes de armazenar informações.
Insecure Temporary File
Exemplo: O código a seguir usa um arquivo temporário para armazenar dados intermediários coletados da rede antes que eles sejam processados.
...
if (tmpnam_r(filename)){
FILE* tmp = fopen(filename,"wb+");
while((recv(sock,recvbuf,DATA_SIZE, 0) > 0)&&(amt!=0))
amt = fwrite(recvbuf,1,DATA_SIZE,tmp);
}
...
Esse código, que de outra forma seria desinteressante, é vulnerável a uma série de ataques diferentes, pois se baseia em um método inseguro para a criação de arquivos temporários. As vulnerabilidades introduzidas por essa função e outras estão descritas nas próximas seções. Os problemas de segurança mais graves relacionados com a criação de arquivos temporários têm ocorrido em sistemas operacionais baseados no Unix, mas os aplicativos do Windows apresentam riscos paralelos. Esta seção inclui uma discussão sobre a criação de arquivos temporários em sistemas Unix e Windows.
Os métodos e comportamentos podem variar entre sistemas, mas os riscos fundamentais introduzidos por cada um são razoavelmente constantes. Consulte a seção Recomendações para obter informações sobre funções básicas de linguagem de segurança e orientações a respeito de uma abordagem segura para a criação de arquivos temporários.
As funções projetadas para auxiliar na criação de arquivos temporários podem ser divididas em dois grupos com base no fato de simplesmente fornecerem um nome de arquivo ou de realmente abrirem um novo arquivo.
Grupo 1 - Nomes de arquivos "exclusivos":
O primeiro grupo de funções da Biblioteca C e da WinAPI projetadas para ajudar no processo de criação de arquivos temporários trabalha gerando um nome de arquivo exclusivo para um novo arquivo temporário, que o programa então deve abrir. Esse grupo inclui funções da Biblioteca C como
tmpnam()
, tempnam()
, mktemp()
e seus equivalentes em C ++ prefaciados com um _
(sublinhado), bem como a função GetTempFileName()
da API do Windows. Esse grupo de funções é afetado por uma condição de corrida subjacente no nome do arquivo escolhido. Embora as funções garantam que o nome do arquivo é exclusivo no momento em que é selecionado, não existe nenhum mecanismo para impedir que outro processo ou um invasor crie um arquivo com o mesmo nome após essa seleção, mas antes de o aplicativo tentar abri-lo. Além do risco de uma colisão legítima causada por outra chamada para a mesma função, há uma alta probabilidade de que um invasor seja capaz de criar uma colisão mal-intencionada, pois os nomes de arquivos gerados por essas funções não são suficientemente aleatórios a ponto de os tornar difíceis de adivinhar.Se um arquivo com o nome selecionado for criado, dependendo de como esse arquivo for aberto, seu conteúdo ou suas permissões de acesso existentes poderão permanecer intactos. Se o conteúdo existente do arquivo for realmente mal-intencionado, um invasor talvez seja capaz de injetar dados perigosos no aplicativo quando este lê dados provenientes do arquivo temporário. Se um invasor criar o arquivo previamente com permissões brandas de acesso, os dados armazenados no arquivo temporário pelo aplicativo poderão ser acessados, modificados ou corrompidos por esse invasor. Em sistemas baseados no Unix, um ataque ainda mais traiçoeiro é possível quando o invasor cria o arquivo previamente como um link para outro arquivo importante. Dessa forma, se o aplicativo truncar ou gravar dados no arquivo, ele poderá realizar involuntariamente operações prejudiciais para favorecer o invasor. Trata-se de uma ameaça especialmente grave nos casos em que o programa opera com permissões elevadas.
Por fim, na melhor das hipóteses, o arquivo será aberto com uma chamada para
open()
usando os sinalizadores O_CREAT
e O_EXCL
ou para CreateFile()
usando o atributo CREATE_NEW
, o que falhará se o arquivo já existir, impedindo assim os tipos de ataques descritos anteriormente. No entanto, se um invasor for capaz de prever com precisão uma sequência de nomes de arquivos temporários, o aplicativo poderá ser impedido de abrir o armazenamento temporário necessário, causando um ataque de negação de serviço (DoS). Considerando a pequena quantidade de aleatoriedade usada na seleção dos nomes de arquivos gerados por essas funções, não é difícil elaborar esse tipo de ataque.Grupo 2 - Arquivos "exclusivos":
O segundo grupo de funções da Biblioteca C tenta resolver alguns dos problemas de segurança relacionados a arquivos temporários, não só gerando um nome de arquivo exclusivo, como também abrindo esse arquivo. Esse grupo inclui funções da Biblioteca C, como
tmpfile()
e seus equivalentes em C++ prefaciados com um _
(sublinhado), bem como a função mkstemp()
da Biblioteca C, que apresenta um comportamento um pouco melhor.As funções ao estilo
tmpfile()
constroem um nome de arquivo exclusivo e abrem esse arquivo da mesma maneira que fopen()
faria se os sinalizadores "wb+"
fossem transmitidos, ou seja, como um arquivo binário no modo de leitura/gravação. Se o arquivo já existir, tmpfile()
vai truncá-lo no tamanho zero, possivelmente em uma tentativa de acalmar as preocupações de segurança mencionadas anteriormente a respeito da condição de corrida existente entre a seleção de um nome de arquivo supostamente exclusivo e a subsequente abertura do arquivo selecionado. No entanto, esse comportamento claramente não resolve os problemas de segurança da função. Em primeiro lugar, um invasor pode criar o arquivo previamente com permissões brandas de acesso que provavelmente serão mantidas pelo arquivo aberto por tmpfile()
. Além disso, em sistemas baseados no Unix, se o invasor criar o arquivo previamente como um link para outro arquivo importante, o aplicativo poderá usar suas permissões possivelmente elevadas para truncar esse arquivo, provocando danos em nome do invasor. Por fim, se tmpfile()
criar um novo arquivo, as permissões de acesso aplicadas a esse arquivo vão variar de um sistema operacional para outro, o que pode deixar os dados do aplicativo vulneráveis mesmo que um invasor não seja capaz de prever com antecedência o nome do arquivo a ser usado.Em última análise,
mkstemp()
é uma forma razoavelmente segura de criar arquivos temporários. Essa função tentará criar e abrir um arquivo exclusivo com base em um modelo de nome de arquivo fornecido pelo usuário, combinado com uma série de caracteres aleatoriamente gerados. Se ela não conseguir criar esse arquivo, falhará e retornará -1
. Em sistemas modernos, o arquivo é aberto com o uso do modo 0600
, o que significa que o arquivo ficará protegido contra adulteração, a menos que o usuário altere explicitamente suas permissões de acesso. No entanto, mkstemp()
ainda sofre com o uso de nomes de arquivos previsíveis e poderá deixar um aplicativo vulnerável a ataques de negação de serviço se um invasor provocar a falha de mkstemp()
ao prever e criar previamente os nomes de arquivo a serem utilizados.Exemplo: O código a seguir usa um arquivo temporário para armazenar dados intermediários coletados da rede antes que eles sejam processados.
...
try:
tmp_filename = os.tempnam()
tmp_file = open(tmp_filename, 'w')
data = s.recv(4096)
while True:
more = s.recv(4096)
tmp_file.write(more)
if not more:
break
except socket.timeout:
errMsg = "Connection timed-out while connecting"
self.logger.exception(errMsg)
raise Exception
...
Esse código, que de outra forma seria desinteressante, é vulnerável a uma série de ataques diferentes, pois se baseia em um método inseguro para a criação de arquivos temporários. As vulnerabilidades introduzidas por essa função e outras estão descritas nas próximas seções. Os problemas de segurança mais graves relacionados com a criação de arquivos temporários têm ocorrido em sistemas operacionais baseados no Unix, mas os aplicativos do Windows apresentam riscos paralelos.
Os métodos e comportamentos podem variar entre sistemas, mas os riscos fundamentais introduzidos por cada um são razoavelmente constantes. Consulte a seção Recomendações para obter informações sobre funções básicas de linguagem de segurança e orientações a respeito de uma abordagem segura para a criação de arquivos temporários.
As funções projetadas para auxiliar na criação de arquivos temporários podem ser divididas em dois grupos com base no fato de simplesmente fornecerem um nome de arquivo ou de realmente abrirem um novo arquivo.
Grupo 1 - Nomes de arquivos "exclusivos":
O primeiro grupo de funções projetadas para ajudar com o processo de criação de arquivos temporários o faz ao gerar um nome de arquivo exclusivo para um novo arquivo temporário, que o programa deveria então abrir. Esse grupo de funções é afetado por uma condição de corrida subjacente no nome do arquivo escolhido. Embora as funções garantam que o nome do arquivo é exclusivo no momento em que é selecionado, não existe nenhum mecanismo para impedir que outro processo ou um invasor crie um arquivo com o mesmo nome após essa seleção, mas antes de o aplicativo tentar abri-lo. Além do risco de uma colisão legítima causada por outra chamada para a mesma função, há uma alta probabilidade de que um invasor seja capaz de criar uma colisão mal-intencionada, pois os nomes de arquivos gerados por essas funções não são suficientemente aleatórios a ponto de os tornar difíceis de adivinhar.
Se um arquivo com o nome selecionado for criado, dependendo de como esse arquivo for aberto, seu conteúdo ou suas permissões de acesso existentes poderão permanecer intactos. Se o conteúdo existente do arquivo for realmente mal-intencionado, um invasor talvez seja capaz de injetar dados perigosos no aplicativo quando este lê dados provenientes do arquivo temporário. Se um invasor criar o arquivo previamente com permissões brandas de acesso, os dados armazenados no arquivo temporário pelo aplicativo poderão ser acessados, modificados ou corrompidos por esse invasor. Em sistemas baseados no Unix, um ataque ainda mais traiçoeiro é possível quando o invasor cria o arquivo previamente como um link para outro arquivo importante. Dessa forma, se o aplicativo truncar ou gravar dados no arquivo, ele poderá realizar involuntariamente operações prejudiciais para favorecer o invasor. Trata-se de uma ameaça especialmente grave nos casos em que o programa opera com permissões elevadas.
Finalmente, no melhor dos casos, o arquivo será aberto com uma chamada de
open()
usando os sinalizadores os.O_CREAT
e os.O_EXCL
, que falharão se o arquivo já existir e, portanto, evitarão os tipos de ataques anteriormente descritos. No entanto, se um invasor for capaz de prever com precisão uma sequência de nomes de arquivos temporários, o aplicativo poderá ser impedido de abrir o armazenamento temporário necessário, causando um ataque de negação de serviço (DoS). Considerando a pequena quantidade de aleatoriedade usada na seleção dos nomes de arquivos gerados por essas funções, não é difícil elaborar esse tipo de ataque.Grupo 2 - Arquivos "exclusivos":
O segundo grupo de funções tenta resolver alguns dos problemas de segurança relacionados aos arquivos temporários ao gerar um nome de arquivo exclusivo e abrir o arquivo. Esse grupo inclui funções como
tmpfile()
.As funções ao estilo
tmpfile()
constroem um nome de arquivo exclusivo e abrem esse arquivo da mesma maneira que open()
faria se os sinalizadores "wb+"
fossem transmitidos, ou seja, como um arquivo binário no modo de leitura/gravação. Se o arquivo já existir, tmpfile()
vai truncá-lo no tamanho zero, possivelmente em uma tentativa de acalmar as preocupações de segurança mencionadas anteriormente a respeito da condição de corrida existente entre a seleção de um nome de arquivo supostamente exclusivo e a subsequente abertura do arquivo selecionado. No entanto, esse comportamento claramente não resolve os problemas de segurança da função. Em primeiro lugar, um invasor pode criar o arquivo previamente com permissões brandas de acesso que provavelmente serão mantidas pelo arquivo aberto por tmpfile()
. Além disso, em sistemas baseados no Unix, se o invasor criar o arquivo previamente como um link para outro arquivo importante, o aplicativo poderá usar suas permissões possivelmente elevadas para truncar esse arquivo, provocando danos em nome do invasor. Por fim, se tmpfile()
criar um novo arquivo, as permissões de acesso aplicadas a esse arquivo vão variar de um sistema operacional para outro, o que pode deixar os dados do aplicativo vulneráveis mesmo que um invasor não seja capaz de prever com antecedência o nome do arquivo a ser usado.