Чи можу я попросити браузер не запускати <script> s в межах елемента?


84

Чи можна сказати браузерам не запускати JavaScript із певних частин документа HTML?

Люблю:

<div script="false"> ...

Це може бути корисно як додаткова функція безпеки. Усі скрипти, які я хочу, завантажуються у певну частину документа. У інших частинах документа не повинно бути сценаріїв, і якщо вони є, їх не слід запускати.


1
Не те, що я знаю. Це коментар, а не відповідь, тому що я не можу сказати на 100%
freefaller

4
@ chiastic-security Ви можете пройти DOM лише після завантаження DOM. До цього моменту виконується будь-який JS у цьому блоці.
кожухи

8
Найближче, що я можу придумати, - це політика безпеки вмісту, де ви можете обмежувати сценарії за їх походженням (можливо, саме цього ви хочете). Наприклад, вказавши, що script-src:"self"ви дозволяєте запускати на сторінці лише сценарії з вашого домену. Якщо вам цікаво, прочитайте цю статтю від Майка Веста про CSP .
Крістоф

1
@ chiastic-security, як ви плануєте послабити обмеження, зазначені в заголовку, після того, як заголовки вже відправлені? У будь-якому випадку, оскільки CSP за замовчуванням вимикає вбудовані сценарії, а віддалені сценарії не завантажуватимуться, якщо не внесені до білого списку, чому вам потрібно поєднувати їх з чимось іншим? CSP - це, мабуть, найкраща ставка OP; Я сподіваюся, хтось залишив відповідь CSP.
Dagg Nabbit

6
Якщо ви стурбовані тим, що хтось вводить довільні блоки у ваш HTML, як запропоноване вами рішення запобіжить введенню а </div>для закриття цього елемента DOM, а потім запуску нового, <div>який буде братом і сестрою того, де сценарії не запущені?
Damien_The_Unbeliever

Відповіді:


92

ТАК, ви можете :-) Відповідь така: Політика безпеки вмісту (CSP).

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

Але якщо ви можете змінити свій сайт, щоб завантажувати весь JavaScript із зовнішніх файлів JavaScript, ви можете взагалі вимкнути вбудований JavaScript за допомогою цього заголовка!

Ось гарний підручник із прикладом: Підручник з HTML5Rocks

Якщо ви зможете налаштувати сервер для надсилання цього прапора HTTP-заголовка, світ стане кращим!


2
+1 Це насправді дуже класно, я навіть не уявляв, що це існує! (Одна нота, ваша посилання вікі до німецької версії безпосередньо) Ось короткий виклад на підтримку браузера: caniuse.com/#feat=contentsecuritypolicy
BrianH

4
Зауважте, що навіть якщо ви зробите це, дозволяти введення користувачем незаглибленого введення на сторінці все одно є поганою ідеєю. Це в основному дозволило б користувачеві змінити сторінку, щоб виглядати як завгодно. Навіть з усіма налаштуваннями політики безпеки вмісту (CSP), встановленими на максимум (заборона вбудованих сценаріїв, стилів тощо), користувач все одно може виконувати атаку на підробку міжсайтових запитів (CSRF), використовуючи srcs зображення для запитів GET, або обманюючи користувача натискання кнопки подання форми для запитів POST.
Ajedi32

@ Ajedi32 Звичайно, ви завжди повинні дезінфікувати введені користувачем дані. Але CSP може навіть встановлювати політики для запитів GET, таких як зображення або css, він не тільки блокуватиме їх, але навіть інформуватиме про це ваш сервер!
Falco

1
@Falco Я прочитав документи , і я зрозумів, що ви можете обмежити ці запити лише для певного домену, а не для певного набору підсторінок у домені. Імовірно домен, який ви встановили, буде таким же, як і ваш сайт, що означає, що ви все ще будете відкриті для атак CSRF.
Ajedi32

3
@Falco Так, це в основному те, що StackExchange зробив з новою функцією " Сніппети стека": 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="//" />.


Схоже, скрипка працює не так, як очікувалося. Я не повинен бачити "заблоковану" частину, так?
Салман

@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 виконується "вбудовано", тобто в тому порядку, в якому він відображається в 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>мітку, а потім вколоти те, що їм подобається.
кожухи

Так, правильним рішенням є вирішення проблеми на стороні сервера. Те, що я роблю за допомогою JavaScript, краще робити на стороні сервера.
Салман,

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) {}
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.