Posso pedir a um navegador para não executar <script> s dentro de um elemento?

84

É possível dizer aos navegadores para não executarem JavaScript de partes específicas de um documento HTML?

Gostar:

<div script="false"> ...

Pode ser útil como um recurso de segurança adicional. Todos os scripts que desejo são carregados em uma parte específica do documento. Não deve haver scripts em outras partes do documento e, se houver, não devem ser executados.

Seppo Erviälä
fonte
1
Não que eu saiba. Este é um comentário em vez de uma resposta, porque não posso dizer 100%
freefaller
4
@ chiastic-security Você só pode atravessar o DOM depois que o DOM for carregado. Nesse ponto, qualquer JS naquele bloco teria sido executado.
capuzes de
8
O mais próximo que posso pensar é a política de segurança de conteúdo, onde você pode restringir scripts por sua origem (talvez seja isso o que você deseja). Por exemplo, ao especificar que script-src:"self"você permite que apenas scripts de seu domínio sejam executados na página. Se você estiver interessado, leia este artigo de Mike West sobre CSP .
Christoph
1
@ chiastic-security como você planeja relaxar uma restrição especificada em um cabeçalho depois que os cabeçalhos já tiverem sido enviados? De qualquer forma, como o CSP desabilita scripts embutidos por padrão, e os scripts remotos não carregam a menos que estejam na lista de permissões, por que você precisa combiná-los com qualquer outra coisa? CSP é provavelmente a melhor aposta do OP; Espero que alguém deixe uma resposta CSP.
Dagg Nabbit
6
Se você está preocupado com alguém injetando blocos arbitrários em seu HTML, como sua solução proposta vai evitar que eles injetem um </div>para fechar este elemento DOM e, em seguida, iniciar um novo <div>que será irmão daquele em que os scripts não estão sendo executados?
Damien_The_Unbeliever

Respostas:

92

SIM, você pode :-) A resposta é: Política de Segurança de Conteúdo (CSP).

A maioria dos navegadores modernos suporta este sinalizador , que diz ao navegador apenas para carregar o código JavaScript de um arquivo externo confiável e desabilitar todo o código JavaScript interno! A única desvantagem é que você não pode usar nenhum JavaScript embutido em sua página inteira (não apenas em uma <div>). Embora possa haver uma solução alternativa, incluindo dinamicamente o div de um arquivo externo com uma política de segurança diferente, mas não tenho certeza sobre isso.

Mas se você pode alterar seu site para carregar todo o JavaScript de arquivos JavaScript externos, você pode desativar o JavaScript embutido completamente com este cabeçalho!

Aqui está um bom tutorial com exemplo: HTML5Rocks Tutorial

Se você puder configurar o servidor para enviar esta bandeira HTTP-Header, o mundo será um lugar melhor!

Falco
fonte
2
+1 Isso é realmente muito legal, eu não fazia ideia que existia! (uma observação, seu link wiki é para a versão alemã diretamente) Aqui está o resumo do suporte do navegador: caniuse.com/#feat=contentsecuritypolicy
BrianH
4
Observe que, mesmo se você fizer isso, permitir a entrada do usuário sem escape em uma página ainda é uma má ideia. Isso basicamente permitiria ao usuário alterar a página para ter a aparência que quiser. Mesmo com todas as configurações de Política de segurança de conteúdo (CSP) definidas para o máximo (não permitindo scripts embutidos, estilos, etc), o usuário ainda pode executar um ataque de falsificação de solicitação entre sites (CSRF) usando srcs de imagem para solicitações GET ou enganando o usuário para que faça clicando em um botão de envio de formulário para solicitações POST.
Ajedi32
@ Ajedi32 Claro que você deve sempre higienizar as entradas do usuário. Mas o CSP pode até definir políticas para solicitações GET como imagens ou css, ele não apenas os bloqueará, mas até mesmo informará seu servidor sobre isso!
Falco
1
@Falco Eu li a documentação e entendi que você só pode restringir essas solicitações a um determinado domínio, não a um conjunto específico de subpáginas do domínio. Presumivelmente, o domínio que você definiu seria o mesmo do seu site, o que significa que você ainda estaria aberto a ataques CSRF.
Ajedi32
3
@Falco Sim, isso é basicamente o que StackExchange fez com o novo recurso Stack Snippets: blog.stackoverflow.com/2014/09/… Se você higienizar adequadamente a entrada, um domínio separado não é necessário.
Ajedi32
13

Você pode bloquear o JavaScript carregado por <script>, usando o beforescriptexecuteevento:

<script>
  // Run this as early as possible, it isn't retroactive
  window.addEventListener('beforescriptexecute', function(e) {
    var el = e.target;
    while(el = el.parentElement)
      if(el.hasAttribute('data-no-js'))
        return e.preventDefault(); // Block script
  }, true);
</script>

<script>console.log('Allowed. Console is expected to show this');</script>
<div data-no-js>
  <script>console.log('Blocked. Console is expected to NOT show this');</script>
</div>

Observe que beforescriptexecutefoi definido no HTML 5.0, mas foi removido no HTML 5.1. O Firefox é o único navegador importante que o implementou.

Caso você esteja inserindo um monte de HTML não confiável em sua página, esteja ciente de que o bloqueio de scripts dentro desse elemento não fornecerá mais segurança, porque o HTML não confiável pode fechar o elemento em sandbox e, portanto, o script será colocado fora e executado.

E isso não vai bloquear coisas como <img onerror="javascript:alert('foo')" src="//" />.

Oriol
fonte
O violino não parece funcionar como esperado. Não devo conseguir ver a parte "bloqueada", certo?
Salman A
@SalmanA Exatamente. Provavelmente, seu navegador não suporta beforescriptexecuteeventos. Funciona no Firefox.
Oriol
Provavelmente porque não está funcionando no Chrome, com o snippet conforme fornecido, embora eu veja que você acabou de convertê-lo em um snippet :-)
Mark Hurd
beforescriptexecuteparece que não é compatível e não será compatível com a maioria dos navegadores principais. developer.mozilla.org/en-US/docs/Web/Events/beforescriptexecute
Matt Pennington
8

Pergunta interessante, não acho que seja possível. Mas mesmo que seja, parece que seria um hack.

Se o conteúdo desse div não for confiável, você precisará escapar dos dados no lado do servidor antes que eles sejam enviados na resposta HTTP e renderizados no navegador.

Se você deseja apenas remover <script>tags e permitir outras tags html, apenas retire-as do conteúdo e deixe o resto.

Verifique a prevenção de XSS.

https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet

capuzes
fonte
7

JavaScript é executado "inline", ou seja, na ordem em que aparece no DOM (se não fosse esse o caso, você nunca poderia ter certeza de que alguma variável definida em um script diferente estava visível quando você o usou pela primeira vez )

Isso significa que, em teoria, você poderia ter um script no início da página (ou seja, o primeiro <script>elemento) que examina o DOM e remove todos os <script>elementos e manipuladores de eventos dentro do seu <div>.

Mas a realidade é mais complexa: o carregamento do DOM e do script ocorre de forma assíncrona. Isso significa que o navegador apenas garante que um script possa ver a parte do DOM que está antes dele (ou seja, o cabeçalho até agora em nosso exemplo). Não há garantias para nada além de (isso está relacionado a document.write()). Então você pode ver a próxima tag de script ou talvez não.

Você poderia travar o onloadevento do documento - o que garantiria que você obtivesse todo o DOM - mas, naquele momento, o código malicioso já poderia ter sido executado. As coisas pioram quando outros scripts manipulam o DOM, adicionando scripts lá. Portanto, você também teria que verificar todas as alterações do DOM.

Portanto, a solução @cowls (filtragem no servidor) é a única solução que pode funcionar em todas as situações.

Aaron Digulla
fonte
1

Se você deseja exibir o código JavaScript em seu navegador:

Usando JavaScript e HTML, você terá que usar entidades HTML para exibir o código JavaScript e evitar que esse código seja executado. Aqui você pode encontrar a lista de entidades HTML:

Se você estiver usando uma linguagem de script do lado do servidor (PHP, ASP.NET, etc.), muito provavelmente, há uma função que escaparia de uma string e converteria os caracteres especiais em entidades HTML. Em PHP, você usaria "htmlspecialchars ()" ou "htmlentities ()". O último cobre todos os caracteres HTML.

Se você deseja exibir seu código JavaScript de uma maneira agradável, tente um dos marcadores de código:

Wissam El-Kik
fonte
1

Eu tenho uma teoria:

  • Envolva a parte específica do documento dentro de uma noscripttag.
  • Use as funções DOM para descartar todas as scripttags dentro da noscripttag e desembrulhe seu conteúdo.

Exemplo de prova de conceito:

window.onload = function() {
    var noscripts = /* _live_ list */ document.getElementsByTagName("noscript"),
        memorydiv = document.createElement("div"),
        scripts = /* _live_ list */ memorydiv.getElementsByTagName("script"),
        i,
        j;
    for (i = noscripts.length - 1; i >= 0; --i) {
        memorydiv.innerHTML = noscripts[i].textContent || noscripts[i].innerText;
        for (j = scripts.length - 1; j >= 0; --j) {
            memorydiv.removeChild(scripts[j]);
        }
        while (memorydiv.firstChild) {
            noscripts[i].parentNode.insertBefore(memorydiv.firstChild, noscripts[i]);
        }
        noscripts[i].parentNode.removeChild(noscripts[i]);
    }
};
body { font: medium/1.5 monospace; }
p, h1 { margin: 0; }
<h1>Sample Content</h1>
<p>1. This paragraph is embedded in HTML</p>
<script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
<p>3. This paragraph is embedded in HTML</p>
<h1>Sample Content in No-JavaScript Zone</h1>
<noscript>
    <p>1. This paragraph is embedded in HTML</p>
    <script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
    <p>3. This paragraph is embedded in HTML</p>
</noscript>
<noscript>
    <p>1. This paragraph is embedded in HTML</p>
    <script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
    <p>3. This paragraph is embedded in HTML</p>
</noscript>

Salman A
fonte
Se o conteúdo do div não for confiável, acho que é a questão. Eles podem simplesmente fechar a <noscript>tag e injetar o que quiserem.
capuzes
Sim, a solução adequada é corrigir o problema no lado do servidor. O que estou fazendo via JavaScript deve ser feito melhor no lado do servidor.
Salman A de
0

Se você quiser reativar as tags de script mais tarde, minha solução foi interromper o ambiente do navegador para que qualquer script executado gerasse um erro bem cedo. No entanto, não é totalmente confiável, então você não pode usá-lo como um recurso de segurança.

Se você tentar acessar as propriedades globais, o Chrome lançará uma exceção.

setTimeout("Math.random()")
// => VM116:25 Uncaught Error: JavaScript Execution Inhibited  

Estou substituindo todas as propriedades substituíveis em window, mas você também pode expandi-lo para interromper outra funcionalidade.

window.allowJSExecution = inhibitJavaScriptExecution();
function inhibitJavaScriptExecution(){

    var windowProperties = {};
    var Object = window.Object
    var console = window.console
    var Error = window.Error

    function getPropertyDescriptor(object, propertyName){
        var descriptor = Object.getOwnPropertyDescriptor(object, propertyName);
        if (!descriptor) {
            return getPropertyDescriptor(Object.getPrototypeOf(object), propertyName);
        }
        return descriptor;
    }

    for (var propName in window){
        try {
            windowProperties[propName] = getPropertyDescriptor(window, propName)
            Object.defineProperty(window, propName, {
                get: function(){
                    throw Error("JavaScript Execution Inhibited")
                },
                set: function(){
                    throw Error("JavaScript Execution Inhibited")
                },
                configurable: true
            })
        } catch (err) {}
    }

    return function allowJSExecution(){
        for (var propName in window){
            if (!(propName in windowProperties)) {
                delete windowProperties[propName]
            }
        }

        for (var propName in windowProperties){
            try {
                Object.defineProperty(window, propName, windowProperties[propName])
            } catch (err) {}
        }
    }
}
Matt Zeunert
fonte