Por que eval() é tão perigoso?
A FUNÇÃO EVAL
A função eval() dentro do javascript é uma função que avalia uma expressão enviada como string por parâmetro a ela, portanto, o que vier dentro dela será executado.
Por exemplo, se caso enviarmos como parâmetro "1 + 1" como string, a função irá avaliar essa expressão e retornará seu resultado, que no caso será o numero 2!
Essa função serve também para fazermos um parse de valores recebidos como parâmetro e vamos ver isso em um exemplo prático.
const express = require('express')
express().get('/number', async(req, res) => {
const numberOne = eval(req.query.numberOne)
const numberTwo = eval(req.query.numberTwo)
const response = numberOne * numberTwo
res.end(`response number: ${response}`)
})
.listen(3000, () => console.log('Rodando server'))
Aqui temos uma api simples onde usamos o express para facilitar a criação. Na rota "/number" é enviado por query os parâmetros numberOne e numberTwo, e a api retorna a multiplicação desses dois números. Esses numeros são enviados como string e passando esses parâmetros para dentro da função eval() ela retorna eles como sendo do tipo number. Legal não é mesmo? NÃO, não é nada legal!
Como explicamos antes, a função avalia a expressão enviada para ela e executa o que tivermos lá dentro. A seguir vamos ver uma requisição feita e a resposta recebida de acordo com o que em teoria deveria acontecer!
curl -s "http://localhost:3000/number?numberOne=3&numberTwo=5"
response number: 15
Funcionando perfeitamente! Mas agora vamos brincar um pouco e enviar uma função javascript como parametro no numberTwo
//Função enviada
require('fs').readdir(__dirname,(err, files)=>console.log(files))
Resultado:
curl -s "http://localhost:3000/number?numberOne=3&numberTwo=require('fs').readdir(__dirname,(err, files)=>console.log(files))"
response number: NaN
Como resultado, tivemos um NaN. Parece que não aconteceu nada demais, porém dentro do servidor nós teremos este resultado:
[ 'node_modules', 'package-lock.json', 'package.json', 'server.js' ]
Injetamos uma função javascript e a função eval executou o comando dentro do servidor.
Isso é bem ruim não é mesmo? Sim, mas vamos piorar ainda mais! Que tal passamos para um DoS total? Vamos lá?
Basta pegarmos o mesmo curl e enviarmos uma nova função para dentro do parâmetro numberTwo
//Função enviada
process.kill(process.pid)
resultado:
curl -s "http://localhost:3000/number?numberOne=3&numberTwo=process.kill(process.pid)"
assim que fizer essa requisição, você não vai receber nenhuma resposta, simplesmente por que a aplicação será encerrada na mesma hora!
COMO SE PROTEGER?
Nós chamamos de normalização todo processo de sanitização de conteúdos externos, retirando tudo o que não é necessário e garantindo que nossa aplicação fique mais segura.
Um exemplo simples de sanitização feita no código.
const express = require('express')
express().get('/number', async(req, res) => {
const numberOne = parseInt(req.query.numberOne)
const numberTwo = parseInt(req.query.numberTwo)
const response = numberOne * numberTwo
res.end(`response number: ${response}`)
})
.listen(3000, () => console.log('Rodando server'))
Apenas trocamos a função eval() por parseInt() e isso já trás uma segurança gigantesca. Caso tentamos novamente fazer a requisição, este será o resultado.
curl -s "http://localhost:3000/number?numberOne=3&numberTwo=process.kill(process.pid)"
response number: NaN
A resposta será um NaN por que a função parseInt vai tentar fazer a conversão da string e não vai conseguir. Vai retornar um erro, porém isso pode ser tratado e a aplicação pode seguir funcionando normalmente.
CONCLUSÃO
Vale lembrar que o express já da alguma ajuda para nós programadores, então, caso seja enviado algo por parâmetro, esse parâmetro será transformado em uma string, porém essa solução não é um bala de prata, nós programadores precisamos ter noções de segurança, para assim, conseguir garantir aplicações melhores para nós e para nossos usuários.
Desde já agradeço a sua presença nesse meu primeiro artigo, e até uma próxima.