Alura > Cursos de DevOps > Cursos de Segurança > Conteúdos de Segurança > Primeiras aulas do curso Desenvolvimento Seguro: lidando com erros e logs em uma aplicação

Desenvolvimento Seguro: lidando com erros e logs em uma aplicação

Tratando erros de forma segura - Apresentação

Olá, boas-vindas ao curso de Desenvolvimento Seguro. Sou o Geovane Fedrecheski e serei seu instrutor.

Audiodescrição: Geovane é um homem de pele clara, barba e cabelos curtos também claros. Usa óculos de armação redonda na cor preta e camiseta cinza-claro. Ao fundo, uma parede lisa com iluminação azul.

Este curso é para você, pessoa desenvolvedora de software que deseja aprimorar a segurança de suas aplicações.

O que aprenderemos

Para aprendermos tudo isso, usaremos a VollMed, uma aplicação que realiza o gerenciamento de uma clínica na qual é possível cadastrar pacientes e realizar o login.

Nesse curso, focaremos no back-end, ou seja, trabalharemos com uma API Rest melhorando os logs e erros.

Pré-requisitos

Para realizar esse curso, é muito importante que você saiba sobre linha de comando e tenha conhecimento básico em NodeJS, HTTP e APIs.

Além dos vídeos, você também terá atividades, acesso ao Fórum para tirar dúvidas e ao Discord para interagir com a comunidade.

Te esperamos no próximo vídeo!

Tratando erros de forma segura - Encontrando dados sensíveis nos erros

Vamos começar a praticar. Para isso, abrimos o projeto no GitHub.

Usaremos a API ValMed, um sistema de gerenciamento de clínicas que possui funcionalidades como cadastro de pacientes e agendamento de exames.

Trabalharemos principalmente com o back-end. Se você já concluiu o curso anterior já deve estar familiarizado.

Com o código já baixado e a pasta aberta no terminal, vamos executá-lo passando o comando docker-compose up, seguido de "Enter".

docker-compose up

Enquanto carrega, analisaremos o Trello, outra ferramenta que utilizaremos nesse curso para organizar o trabalho e tarefas.

Durante o curso, teremos demandas de segurança para serem aplicadas na API. Então, no Trello, temos uma coluna destinada às demandas, outra referente as tarefas a fazer, tarefas em andamento e finalizadas.

Nesse vídeo, desenvolveremos a tarefa "Informações sensíveis em mensagens de erro", que sugere que as mensagens de erro da API da VollMed estão revelando informações sensíveis sobre o sistema.

Corrigindo informações sensíveis em mensagens de erro

Antes de fazermos isso, verificaremos que o servidor já inicializou. Para isso, voltamos no terminal. Se a mensagem for "App Data Source inicializado", significa que tudo correu bem.

Faremos um teste utilizando a ferramenta Insomnia, afinal, por meio dela podemos fazer requisições. No menu lateral esquerdo, encontramos requisições pré-configuradas, a opção selecionada é o POST do cadastro do paciente.

Você encontra instruções sobre a configuração do Insomnia no Preparando Ambiente.

No centro da tela podemos visualizar o código com os dados do paciente. Para testarmos, clicamos no botão "Send", localizado no topo superior direito da tela.

Feito isso, aparece a mensagem "202 Accepted" e abaixo o resultado, isso significa que deu certo. Porém, relembrando, segundo as especificações do Trello, se inserirmos o campo de imagem, haverá um erro e mensagens sensíveis serão exibidas.

Para corrigir, precisamos inserir o campo "imagem": "uma_bela_imagem.png" em um objeto paciente. Então, copiamos esse código, voltamos no Insomnia e colamos na linha 17. Depois, clicamos em "Send".

//Trecho omitido

"senha": "abCD12!@",
"telefone": "11988887777",
"possuiPlanoSaude": true,
"planosSaude": [3,2,5),
"imagem": "uma_bela_imagem.png",
"historico": ["bronquiete, leve", "sinusite, moderado"]
}

Repare que ao fazer isso, aparece a mensagem "409 Conflict", pois já tinha um paciente com esse CPF. Então, no navegador, acessamos o Gerador de CPF, copiamos e colamos no Insomnia. Feito isso, novamente clicamos em "Send".

Agora tivemos um erro "502 Bad Gateway". A ferramenta indica que as informações, como id, CPF e e-mail, fazem parte do banco de dados da VollMed, ou seja, são dados sensíveis que não tornamos público. Isso porque pessoas podem utilizar esses dados contra o próprio sistema. Então, a mensagem de erro faz todo sentido.

Analisando o restante do retorno, também notamos códigos de erro relacionados ao driver do banco de dados. A partir disso, a pessoa atacante pode descobrir a versão do banco de dados e encontrar uma falha de segurança. Sendo assim, nunca é apropriado deixar visível para o usuário final informações internas do sistema.

Para resolvermos isso, copiamos a mensagem de erro "Paciente não foi criado". Depois, abrimos o VS Code, na barra de menu lateral, clicamos no ícone identificado por uma lupa, colamos e apertamos "Enter".

Feito isso, esse trecho de código é encontrado no arquivo pacienteController.ts na linha 112. Analisando o código, descobrimos que essa linha é chamada na função criarPaciente. Isso significa que faz sentido, pois a rota que usamos no Insomnia era para criar paciente.

No início dessa função, logo que iniciada, há um try. Dessa forma, toda exceção estará sob gerenciamento, caso ocorra uma. Mais abaixo, notamos que dentro do try há um cath que exibe o erro, caso tenha um.

É nesse trecho que a mensagem "Paciente não foi criado" foi definida. Portanto, o erro é passado na sua totalidade, é completamente retornado para o front-end, é isso que queremos evitar.

Sendo assim, na linha 113, apagamos o trecho error. Antes de 'Paciente não foi criado' escrevemos message seguido de dois pontos.

//Trecho omitido

    res.status(202).json(pacienteSemDadosSensiveis)
  } catch (error) {
    if (error.name === 'ValidationError') {
      res.status(400).json({ message: error.message })
    } else {
      res.status(502).json({ message: 'Paciente não foi criado' })
      console.log(error)
    }
  }
}

Assim, deixamos o código mais sucinto e removemos as informações sensíveis. Ao salvar nosso código já é recompilado. Para conferirmos se deu certo, abrimos o terminal. Se aparecer uma mensagem informando que foi inicializado e não foi encontrado nenhum erro é que deu certo.

Voltamos no Insomnia, usaremos a mesma requisição, então clicamos em "Send". Feito isso, aparece a mensagem de erro "502 Bad Gateway", que é o que esperávamos, porém, agora o erro está mais resumido, sem informações sensíveis ou detalhes do sistema.

Voltamos no Trello para conferir se fizemos todas as correções necessárias. Ao abrir o cartão, logo abaixo, encontramos um checklist da OWASP, uma fundação que define diretrizes para criar códigos seguros.

Nesse caso, temos as seguintes diretrizes referentes aos erros:

Como realizamos todas essas correções, marcamos como completas. Feito isso, movemos esse card para a coluna "Feito", pois concluímos a primeira tarefa para tornar a aplicação da VollMed mais segura.

No próximo vídeo, aprenderemos como unificar o tratamento de erro.

Até lá!

Tratando erros de forma segura - Tratando erros de forma segura

No vídeo anterior, no arquivo pacienteController.ts, fizemos uma modificação na função de criarPaciente(), removendo o trecho referente ao envio de dados sensíveis.

Se analisarmos como estamos tratando os erros nesse código, notamos algumas diferenças. Por exemplo, na linha 59, quando validamos o CPF, lançamos uma exceção. Isso faz sentido, pois estamos dentro de um try, então, quando ocorre um erro, é lançado uma exceção.

Porém, em alguns outros casos, temos caminhos que são erros, como na linha 66, em if (existePacienteComCPF), porém, não lançamos uma exceção, apenas enviamos a resposta da solicitação HTTP.

Isso torna o código inconsistente. Inclusive, no Trello, encontramos um card chamado "Unificar tratamento de erros". Ao abri-lo, descobrimos que a tarefa é usar a classe AppError, pois todos os erros passam por ela. Portanto, se precisarmos fazer uma modificação, o código estará mais homogêneo e será mais fácil de mudar.

Voltamos ao VS Code para fazermos a aplicação.Na linha 67, ao invés de enviarmos uma resposta com um status que vamos escrever manualmente, inserimos uma nova linha abaixo de if (existentePAcienteComCPF) e escrevemos throw new AppError(). Dentro dos parênteses, entre aspas simples, passamos Já existe um paciente com este CPF, seguido de Status.CONFLICT, que tem o código 409.

Feito isso, apagamos o trecho de código anterior. Ficando dessa forma:

//Trecho omitido

const existePacienteComCPF = await AppDataSource.getRepository(Paciente).findOne({
    where: { cpf }
})
if (existePacienteComCPF != null) {
    throw new AppError('Já existe um paciente com esse CPF!', Status.CONFLICT)
}

Usando o error handler global

Lembrando que a classe AppError já trata a questão do statusCode. Então, podemos passar o status para a classe AppError, que atua como ErrorHandler.ts a fim de unificar o tratamento de erros. Isso significa que não precisamos enviar manualmente a resposta do HTTP, porque o tratamento de erros cuidará disso.

Em seguida, continuamos analisando o código e buscando tratamentos de erro que não estejam seguindo a diretriz. Repare que na linha 108, no momento em que é feito o catch do try, temos dois casos.

O primeiro é o teste de validação, então, se vem um erro de validação, será enviado a resposta. Porém, nesse caso não faz sentido, pois já tratamos os erros de validação. Então apagamos da linha 109, if (error.name === 'ValidationError') até a linha 111. Deixando somente o trecho de código para quando o paciente não for criado. Dessa forma:

//Trecho omitido

res.status(202).json(pacienteSemDadosSensiveis)
  } catch (error) {
    res.status(502).json({ 'PAciente não foi criado' })
    console.log(error)
  }
}

Porém, como estamos enviando a resposta manualmente, faremos o mesmo. Então, abaixo de catch(error) escrevemos throw new AppError() passando, dentro de chaves e aspas duplas, a mensagem Paciente não foi criado, Status BAD_GATEWAY e removemos a linha anterior.

Em seguida, movemos a linha de código console.log(error) 1 para baixo de catch(error) Dessa forma:

//Trecho omitido

res.status(202).json (pacienteSemDados Sensiveis)
} catch (error)
console.log(error) 1
throw new AppError('Paciente não foi criado', Status.
BAD_GATEWAY)
}

Como retiramos a parte de validação do final do catch, não precisamos colocar o try no início do código. Então, na linha 38 o apagamos. Depois, o adicionamos na linha 90, antes de começarmos a criar o endereço.

//Trecho omitido

try {
    if (endereco !== undefined) {
      enderecoPaciente.cep = endereco.cep
      enderecoPaciente.rua = endereco.rua
      enderecoPaciente.estado = endereco.estado
      enderecoPaciente.numero = endereco.numero
      enderecoPaciente.complemento = endereco.complemento

Agora, estamos tratando os erros de forma mais homogênea utilizando o Error Handler global.

Voltamos no Trello. Agora, vamos conferir se cumprimos toda a checklist da OWASP, assim aprendemos um pouco sobre as diretrizes e s o que estamos mudando.

Desenvolvemos um mecanismo de tratamento de erros que funciona independente de qual porta o servidor está rodando. Além disso, garantimos que todos os erros passassem por um mesmo canal para melhorar a organização do nosso código.

Sendo assim, podemos marcar as duas checklists e arrastar essa tarefa para a coluna "Feito".

No próximo vídeo, aprenderemos como lidar com os logs da aplicação.

Até lá!

Sobre o curso Desenvolvimento Seguro: lidando com erros e logs em uma aplicação

O curso Desenvolvimento Seguro: lidando com erros e logs em uma aplicação possui 86 minutos de vídeos, em um total de 37 atividades. Gostou? Conheça nossos outros cursos de Segurança em DevOps, ou leia nossos artigos de DevOps.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Segurança acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas