roger-melo-treinamentos / curso-de-js-roger-melo

Repositório de informações do CJRM
491 stars 170 forks source link

Resumo da Aula O problema em usar innerHTML - Aula 03-09 da etapa 15 #7864

Closed OnielStgo closed 8 months ago

OnielStgo commented 8 months ago

O problema em usar innerHTML

Como funciona a innerHTML? A innerHTML recebe um string, parsea ela, ou seja, a converte para HTML e depois insere esse HTML na página. Mas acontece isso pode ser uma brecha de segurança, já que o fato de essa string ser parseada para HTML, e esse HTML ser renderizado em uma aplicação, isso abre uma brecha gigantesca para um tipo de ataque comum chamado Cross-site Scripting (XSS).

Hoje, quando se trata de eventos em JavaScript, o recomendado é usar o método "addEventListener" para atrelar eventos em um determinado elemento. Mas temos que saber que existem outras formas de atrelar eventos e elas são:

Event handler properties (propriedades de manipulação de eventos)

Todo elemento do DOM em que é possível atrelar um evento, tem essas propriedades de manipulação de eventos

Exemplo de uso:

const form = document.querySelector('[data-js="add-item-form"]');

const addItems = e => {
  e.preventDefault();
  //aqui vai o código para adicionar itens
  console.log('O item foi adicionado');
}

form.onsubmit = addItems

//então, como podemos ver no código acima, à propriedade "onsubmit" lhe foi adicionada uma função que será executada quando o evento submit acontecer e a mensagem "O item foi adicionado" será mostrada por consola .

Mas acontece que esta forma de lidar com eventos (ou seja, usando "propriedades de manipulação de eventos" ) tem uma desvantagem com respeito ao usar o "addEventListener", já que se agente quiser executar uma segunda função quando o evento submit acontecer, só vai ser executada a segunda função, ou seja, a primeira função não vai ser executada como se mostra a seguir:

const form = document.querySelector('[data-js="add-item-form"]');

const addItems = e => {
  e.preventDefault();
  //aqui vai o código para adicionar items
  console.log('O item foi adicionado');

}

const showMsg = e => {
  e.preventDefault();
  console.log('Oi');
}

form.onsubmit = addItems;
form.onsubmit = showMsg;

//Então, quando o evento "onsubmit" acontecer, por consola só vermos a mensagem "O item foi adicionado" indicando que só foi executada a segunda função (showMsg).

//Mas por que a primeira função não foi executada? Não foi executada porque não é possível adicionar mais de uma função de callback para o mesmo evento quando o tipo de evento é setado a partir de "propriedades de manipulação de eventos". Em outras palavras, o elemento "form" é um objeto, e "onsubmit" é uma propriedade, portanto, ao adicionar a função "showMsg" para "form.onsubmit" rescrevemos o valor anterior, ou seja, rescrevemos a primeira atribuição (a função addItems) que tínhamos feito.

// Então, ao contrário das "propriedades de manipulação de eventos", quando usamos o "addEventListener" podemos adicionar mais de uma função de callback para o mesmo evento.

Eventos inline (inserindo código JavaScipt diretamente em uma tag HTML

Segue exemplo:

<h2 onclick="console.log('Clicou no título')"></h2>

<!-- então, quando o usuário fizer clique no título h2, por consola será mostrada a mensagem "Clicou no título", isto mostra claramente que como valor do evento "onclick" podemos colocar qualquer código JavaScript e ele será executado  -->

Como podemos ver, utilizar "Eventos inline" é uma má prática já que:

Mas, outro problema de usar JavaScript inline, é que também é possível inserir JavaScript inline dentro de uma "template string" como se mostra a seguir:

const msg = `<button onclick="console.log('Clicou no botão')> Clique aqui</button>`

//então, cada vez que o usuário faça clique no botão, por consola será mostrada a mensagem "Clicou no botão", mostrando claramente que como valor de um evento de um elemento inserido em um template string, podemos colocar qualquer código Javascript já e ele será executado. 

O HTML5 e a tag "script"

O HTML5 especifica que uma tag script inserida com innerHTML não é executada.

A seguir vamos ver um exemplo onde vamos supor que nossa aplicação tem um formulário para adicionar nomes de games, e que quando o usuário inserir um game e fizer clique no botão enviar, o evento submit será disparado e o nome do game será renderizado na tela além de inserido em um bando de dados:

const form = document.querySelector('[data-js="add-item-form"]');
const input = document.querySelector('[data-js="input"]');
const feedbackMsg = document.querySelector('[data-js="feedback"]');

const addItems = e => {
  e.preventDefault();
  const inputValue = e.target.input.value;

  feedbackMsg.innerHTML = `<script>${inputValue}</script>`;

  //aqui vai o código adicionar o game no banco de dados
}

form.addEventListener('submit', addItems);

//então, vamos supor que o usuário introduziu no input o seguinte código: <script>console.log('O game foi adicionado')</script>
//então, o que vai ocorrer quando o evento submit acontecer será:
//- a mensagem 'O game foi adicionado' não será mostrada por consola, já que como sabemos, o HTML5 especifica que uma tag script inserida com innerHTML não é executada, ou seja, mesmo que essa tag script seja parseada para HTML, essa tag script não será executada, portanto, não veremos a mensagem por consola, e também esse código não será renderizado na tela (para ver que de fato o elemento "feedbackMsg" tem o valor inserido pelo usuário, temos que inspecionar o elemento "feedbackMsg")
//- mas o código <script>console.log('O game foi adicionado')</script> sim será adicionado no banco de dados (isto é um problema de segurança)

Mas, será que nossa aplicação estará segura porque o HTML5 especifica que uma tag script inserida com innerHTML não é executada? Não, mesmo assim a nossa aplicação não está segura, já que se o usuário inserir o seguinte código no input:

<img src="x" onerror="alert('Este código foi executado')" />

este código sim será executado quando o evento sumbmit acontecer, e portanto na tela veremos um pop-up dizendo 'Este código foi executado'. Mas porqué o alert sim foi executado? Foi executado porque a innerHTML parseu a string para HTML quando a página foi carregada, e como o valor do atributo src é "x", isso causou um erro ao tentar renderizar a suposta "imagen", então, o evento "onerror" foi disparado e o alert foi executado (lembrando que qualquer código JavaScript pode ser executado quando inserido como valor de um evento). E por último, essa tag img será inserida no banco de dados (isto é um problema de segurança).

Agora, reforçando a última ideia, vamos dizer que qualquer código JavaScript pode ser executado sempre que seja atribuído para um evento (no exemplo anterior foi no evento onerror), e isso significa que um atacante pode executar um código que pega dados do local storage, ou remove tudo o conteúdo da página, ou faz uma requisição, ou pega dados da aplicação e salva eles em um servidor externo, ou obtém o que está sendo digitado, ou tudo isso e muito mais.

Então, o que descrevemos no parágrafo anterior, é o que se conhece como Cross-site Scripting (XSS), o que é um tipo de vulnerabilidade de segurança que pode ser encontrado em aplicações webs. Ou seja, o Cross-site Scripting (XSS) permite que um atacante injete scripts dentro de aplicações webs visualizadas por outros usuários.