Могу ли я попросить браузер не запускать <script> внутри элемента?

84

Is it possible to tell browsers to not run JavaScript from specific parts of an HTML document?

Like:

<div script="false"> ...

It could be useful as an additional security feature. All the scripts I want are loaded in a specific part of the document. There should be no scripts in other parts of the document and if there are they should not be run.

Seppo Erviälä
источник
1
Not that I'm aware of. This is a comment rather than an answer, because I can't say 100%
freefaller
4
@chiastic-security You can only traverse the DOM after the DOM is loaded. By which point any JS in that block would have executed.
cowls
8
The closest I can think of is the content security policy, where you can restrict scripts by their origin (perhaps that is what you want). E.g. by specifying script-src:"self" you allow only scripts from your domain to run in the page. If you are interested, read this article from Mike West about CSP.
Christoph
1
@chiastic-security how do you plan to relax a restriction specified in a header after the headers have already been sent? Anyway, since CSP disables inline scripts by default, and remote scripts won't load unless whitelisted, why would you need to combine it with anything else? CSP is probably the OP's best bet; I hope someone leaves a CSP answer.
Dagg Nabbit
6
If you're concerned about someone injecting arbitrary blocks into your HTML, how is your proposed solution going to prevent them injecting a </div> to close this DOM element and then starting a new <div> that will be a sibling of the one where scripts aren't running?
Damien_The_Unbeliever

Ответы:

92

ДА, вы можете :-) Ответ: Политика безопасности контента (CSP).

Большинство современных браузеров поддерживают этот флаг , который указывает браузеру загружать только код JavaScript из надежного внешнего файла и запрещает весь внутренний код JavaScript! Единственным недостатком является то, что вы не можете использовать какой-либо встроенный JavaScript на всей странице (не только для одной <div>). Хотя может быть обходной путь путем динамического включения div из внешнего файла с другой политикой безопасности, но я не уверен в этом.

But if you can change your site to load all JavaScript from external JavaScript files then you can disable inline JavaScript altogether with this header!

Here is a nice tutorial with example: HTML5Rocks Tutorial

If you can configure the server to send this HTTP-Header flag the world will be a better place!

Falco
источник
2
+1 That's actually really cool, I had no idea that existed! (one note, your wiki link is to the German version directly) Here's the rundown on browser support: caniuse.com/#feat=contentsecuritypolicy
BrianH
4
Обратите внимание, что даже если вы это сделаете, разрешить ввод пользователя на странице без экранирования - это плохая идея. Это в основном позволит пользователю изменять страницу так, как он хочет. Даже если все параметры политики безопасности контента (CSP) установлены на максимум (запрещение встроенных скриптов, стилей и т. Д.), Пользователь все равно может выполнить атаку с подделкой межсайтового запроса (CSRF), используя srcs изображения для запросов GET или обманывая пользователя нажав кнопку отправки формы для запросов POST.
Ajedi32
@ Ajedi32 Конечно, вы всегда должны дезинфицировать вводимые пользователем данные. Но CSP может даже устанавливать политики для GET-запросов, таких как изображения или CSS, он не только блокирует их, но даже сообщает об этом вашему серверу!
Falco
1
@Falco Я прочитал документацию и понял, что вы можете ограничить эти запросы только определенным доменом, а не конкретным набором подстраниц в домене. Предположительно, установленный вами домен будет таким же, как и ваш сайт, а это означает, что вы все равно будете уязвимы для атак CSRF.
Ajedi32
3
@Falco Да, в основном это то, что StackExchange сделал с новой функцией Stack Snippets : blog.stackoverflow.com/2014/09/… Если вы правильно дезинфицируете ввод, отдельный домен не нужен.
Ajedi32
13

Вы можете заблокировать загрузку JavaScript <script>, используя beforescriptexecuteсобытие:

<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>

Обратите внимание, что это beforescriptexecuteбыло определено в HTML 5.0, но было удалено в HTML 5.1. Firefox - единственный крупный браузер, в котором это реализовано.

В случае, если вы вставляете ненадежную группу HTML на свою страницу, имейте в виду, что блокирующие скрипты внутри этого элемента не обеспечат большей безопасности, потому что ненадежный HTML может закрыть изолированный элемент, и, таким образом, скрипт будет размещен снаружи и запущен.

И это не заблокирует такие вещи, как <img onerror="javascript:alert('foo')" src="//" />.

Ориол
источник
Скрипка работает не так, как ожидалось. Я не должен видеть "заблокированную" часть, верно?
Salman A
@SalmanA Совершенно верно. Возможно, ваш браузер не поддерживает beforescriptexecuteсобытие. Он работает в Firefox.
Oriol
Вероятно, потому что он не работает в Chrome с предоставленным фрагментом, хотя я вижу, что вы только что преобразовали его во фрагмент :-)
Марк Херд
beforescriptexecuteпохоже, что он не поддерживается и не будет поддерживаться большинством основных браузеров. developer.mozilla.org/en-US/docs/Web/Events/beforescriptexecute
Мэтт Пеннингтон
8

Интересный вопрос, не думаю, что это возможно. Но даже если это так, похоже, что это взлом.

Если содержимое этого div не является доверенным, вам необходимо избежать данных на стороне сервера, прежде чем они будут отправлены в ответе HTTP и отображены в браузере.

Если вы хотите только удалить <script>теги и разрешить другие теги HTML, просто удалите их из содержимого и оставьте все остальное.

Изучите предотвращение XSS.

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

капоты
источник
7

JavaScript выполняется «inline», то есть в том порядке, в котором он появляется в DOM (если бы это было не так, вы никогда не могли быть уверены, что какая-то переменная, определенная в другом скрипте, была видна, когда вы использовали ее в первый раз. ).

Теоретически это означает, что у вас может быть сценарий в начале страницы (т.е. первый <script>элемент), который просматривает DOM и удаляет все <script>элементы и обработчики событий внутри вашего <div>.

Но в реальности все сложнее: загрузка DOM и скрипта происходит асинхронно. Это означает, что браузер гарантирует только то, что сценарий может видеть часть DOM, которая находится перед ним (то есть заголовок в нашем примере). Нет никаких гарантий ни на что сверх (это связано с document.write()). Так что вы можете увидеть следующий тег скрипта, а может быть, и нет.

Вы могли onloadзафиксировать событие документа - что обеспечило бы получение всей DOM - но в то время вредоносный код мог уже выполняться. Ситуация ухудшается, когда другие сценарии манипулируют DOM, добавляя туда сценарии. Таким образом, вам также придется проверять каждое изменение DOM.

Итак, решение @cowls (фильтрация на сервере) - единственное решение, которое можно заставить работать во всех ситуациях.

Аарон Дигулла
источник
1

Если вы хотите отображать код JavaScript в своем браузере:

Используя JavaScript и HTML, вам придется использовать сущности HTML для отображения кода JavaScript и избегать выполнения этого кода. Здесь вы можете найти список объектов HTML:

Если вы используете язык сценариев на стороне сервера (PHP, ASP.NET и т. Д.), Скорее всего, есть функция, которая экранирует строку и преобразует специальные символы в объекты HTML. В PHP вы должны использовать «htmlspecialchars ()» или «htmlentities ()». Последний охватывает все символы HTML.

Если вы хотите красиво отобразить свой код JavaScript, попробуйте один из маркеров кода:

Виссам Эль-Кик
источник
1

У меня есть теория:

  • Оберните определенную часть документа в noscriptтег.
  • Используйте функции DOM, чтобы отбросить все scriptтеги внутри noscriptтега, а затем развернуть его содержимое.

Пример доказательства концепции:

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>

Салман А
источник
Если содержимое в div не является доверенным, я думаю, ему задают вопрос. Они могли просто закрыть <noscript>бирку, а затем ввести то, что им нравится.
cowls
Да, правильное решение - устранить проблему на стороне сервера. То, что я делаю через JavaScript, лучше делать на стороне сервера.
Salman A
0

Если вы захотите позже снова включить теги скриптов, я решил сломать среду браузера, чтобы любой запускаемый скрипт довольно рано выдавал ошибку. Однако он не совсем надежен, поэтому вы не можете использовать его в качестве средства безопасности.

Если вы попытаетесь получить доступ к глобальным свойствам, Chrome выдаст исключение.

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

Я перезаписываю все перезаписываемые свойства window, но вы также можете расширить его, чтобы нарушить другие функции.

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) {}
        }
    }
}
Мэтт Зойнерт
источник