Czy mogę poprosić przeglądarkę, aby nie uruchamiała <script> w elemencie?

84

Czy można powiedzieć przeglądarkom, aby nie uruchamiały JavaScript z określonych części dokumentu HTML?

Lubić:

<div script="false"> ...

Może być przydatna jako dodatkowa funkcja bezpieczeństwa. Wszystkie skrypty, które chcę, są ładowane w określonej części dokumentu. W innych częściach dokumentu nie powinno być skryptów, a jeśli są, nie należy ich uruchamiać.

Seppo Erviälä
źródło
1
Nie, że jestem świadomy. To raczej komentarz niż odpowiedź, bo nie mogę powiedzieć 100%
freefaller
4
@ chiastic-security Możesz przechodzić przez DOM tylko po załadowaniu DOM. W którym momencie dowolny JS w tym bloku zostałby wykonany.
kaptury
8
Najbliżej przychodzi mi do głowy polityka bezpieczeństwa treści, w której można ograniczyć skrypty według ich pochodzenia (być może właśnie tego chcesz). Np. Określając, script-src:"self"że zezwalasz na uruchamianie na stronie tylko skryptów z Twojej domeny. Jeśli jesteś zainteresowany, przeczytaj artykuł Mike'a Westa o CSP .
Christoph,
1
@ chiastic-security jak planujesz złagodzić ograniczenie określone w nagłówku po wysłaniu nagłówków? W każdym razie, ponieważ CSP domyślnie wyłącza skrypty wbudowane, a zdalne skrypty nie ładują się, chyba że zostaną umieszczone na białej liście, dlaczego miałbyś łączyć to z czymkolwiek innym? CSP jest prawdopodobnie najlepszym wyborem OP; Mam nadzieję, że ktoś zostawi odpowiedź CSP.
Dagg Nabbit,
6
Jeśli obawiasz się, że ktoś wstrzykuje dowolne bloki do Twojego kodu HTML, w jaki sposób proponowane przez Ciebie rozwiązanie uniemożliwi im wstrzyknięcie </div>elementu DOM w celu zamknięcia tego elementu DOM, a następnie rozpoczęcie nowego, <div>który będzie rodzeństwem tego, w którym nie działają skrypty?
Damien_The_Unbeliever

Odpowiedzi:

92

TAK, możesz :-) Odpowiedź brzmi: Polityka bezpieczeństwa treści (CSP).

Większość nowoczesnych przeglądarek obsługuje tę flagę , która mówi przeglądarce tylko, aby ładowała kod JavaScript z zaufanego pliku zewnętrznego i nie zezwalała na cały wewnętrzny kod JavaScript! Jedynym minusem jest to, że nie możesz używać żadnego wbudowanego kodu JavaScript na całej stronie (nie tylko na pojedynczej stronie <div>). Chociaż można by obejść ten problem, dynamicznie dołączając element div z pliku zewnętrznego z inną polityką bezpieczeństwa, ale nie jestem tego pewien.

Ale jeśli możesz zmienić swoją witrynę, aby ładowała wszystkie skrypty JavaScript z zewnętrznych plików JavaScript, możesz całkowicie wyłączyć wbudowany JavaScript za pomocą tego nagłówka!

Oto fajny samouczek z przykładem: Samouczek HTML5Rocks

Jeśli możesz skonfigurować serwer, aby wysyłał tę flagę nagłówka HTTP, świat będzie lepszym miejscem!

Falco
źródło
2
+1 To naprawdę fajne, nie miałem pojęcia, że ​​istnieje! (jedna uwaga, twoje łącze wiki prowadzi bezpośrednio do wersji niemieckiej) Oto podsumowanie obsługi przeglądarek: caniuse.com/#feat=contentsecuritypolicy
BrianH
4
Zwróć uwagę, że nawet jeśli to zrobisz, zezwolenie na wprowadzanie danych bez zmiany znaczenia na stronie jest nadal złym pomysłem. To w zasadzie pozwoliłoby użytkownikowi zmienić wygląd strony tak, jak chce. Nawet przy wszystkich ustawieniach zasad bezpieczeństwa treści (CSP) ustawionych na maksimum (blokowanie skryptów wbudowanych, stylów itp.), Użytkownik nadal może przeprowadzić atak polegający na fałszowaniu żądań między witrynami (CSRF), używając plików obrazów dla żądań GET lub nakłaniając użytkownika do kliknięcie przycisku przesyłania formularza dla żądań POST.
Ajedi32,
@ Ajedi32 Oczywiście należy zawsze czyścić dane wejściowe użytkownika. Ale CSP może nawet ustawić zasady dla żądań GET, takich jak obrazy lub css, nie tylko je zablokuje, ale nawet poinformuje o tym serwer!
Falco
1
@Falco Przeczytałem dokumentację i zrozumiałem, że możesz ograniczyć te żądania tylko do danej domeny, a nie do określonego zestawu podstron w domenie. Przypuszczalnie ustawiona domena byłaby tą samą, co Twoja witryna, co oznacza, że ​​nadal byłbyś otwarty na ataki CSRF.
Ajedi32
3
@Falco Tak, w zasadzie to właśnie zrobił StackExchange z nową funkcją Stack Snippets: blog.stackoverflow.com/2014/09/… Jeśli jednak odpowiednio wyczyścisz dane wejściowe, oddzielna domena nie jest konieczna.
Ajedi32
13

Możesz zablokować ładowanie JavaScript <script>, używając beforescriptexecutezdarzenia:

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

Zauważ, że beforescriptexecutezostało to zdefiniowane w HTML 5.0, ale zostało usunięte w HTML 5.1. Firefox jest jedyną dużą przeglądarką, która go wdrożyła.

Jeśli umieszczasz na swojej stronie niezaufaną wiązkę kodu HTML, pamiętaj, że blokowanie skryptów wewnątrz tego elementu nie zapewni większego bezpieczeństwa, ponieważ niezaufany kod HTML może zamknąć element w piaskownicy, a zatem skrypt zostanie umieszczony na zewnątrz i uruchomiony.

I to nie blokuje takich rzeczy <img onerror="javascript:alert('foo')" src="//" />.

Oriol
źródło
Wydaje się, że skrzypce nie działają zgodnie z oczekiwaniami. Nie powinienem widzieć części „zablokowanej”, prawda?
Salman A
@SalmanA Dokładnie. Prawdopodobnie Twoja przeglądarka nie obsługuje beforescriptexecutewydarzenia. Działa w przeglądarce Firefox.
Oriol
Prawdopodobnie dlatego, że nie działa w Chrome, z dostarczonym fragmentem kodu, chociaż widzę, że właśnie przekonwertowałeś go na fragment :-)
Mark Hurd
beforescriptexecutewygląda na to, że nie jest obsługiwany i nie będzie obsługiwany przez większość głównych przeglądarek. developer.mozilla.org/en-US/docs/Web/Events/beforescriptexecute
Matt Pennington,
8

Ciekawe pytanie, nie sądzę, żeby to było możliwe. Ale nawet jeśli tak jest, wygląda na to, że byłby to hack.

Jeśli zawartość tego elementu div jest niezaufana, musisz uciec przed danymi po stronie serwera, zanim zostaną wysłane w odpowiedzi HTTP i renderowane w przeglądarce.

Jeśli chcesz tylko usunąć <script>tagi i zezwolić na inne tagi HTML, po prostu usuń je z treści i zostaw resztę.

Zapoznaj się z zapobieganiem XSS.

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

osłony
źródło
7

JavaScript jest wykonywany „inline”, tj. W kolejności, w jakiej pojawia się w DOM (gdyby tak nie było, nigdy nie można było być pewnym, że jakaś zmienna zdefiniowana w innym skrypcie była widoczna, gdy używałeś go po raz pierwszy ).

Oznacza to, że teoretycznie możesz mieć skrypt na początku strony (tj. Pierwszy <script>element), który przegląda DOM i usuwa wszystkie <script>elementy i programy obsługi zdarzeń z twojego <div>.

Ale rzeczywistość jest bardziej złożona: ładowanie DOM i skryptu odbywa się asynchronicznie. Oznacza to, że przeglądarka gwarantuje, że skrypt może zobaczyć tylko część DOM, która jest przed nim (tj. Dotychczasowy nagłówek w naszym przykładzie). Nie ma żadnych gwarancji poza (jest to związane document.write()). Więc możesz zobaczyć następny tag skryptu, a może nie.

Możesz dołączyć do onloadzdarzenia dokumentu - co zapewniłoby, że masz cały DOM - ale w tym czasie złośliwy kod mógł już zostać wykonany. Sytuacja pogarsza się, gdy inne skrypty manipulują DOM, dodając tam skrypty. Więc musiałbyś też sprawdzać każdą zmianę DOM.

Tak więc rozwiązanie @cowls (filtrowanie na serwerze) jest jedynym rozwiązaniem, które może działać w każdej sytuacji.

Aaron Digulla
źródło
1

Jeśli chcesz wyświetlić kod JavaScript w swojej przeglądarce:

Używając JavaScript i HTML, będziesz musiał używać encji HTML do wyświetlania kodu JavaScript i unikania wykonywania tego kodu. Tutaj możesz znaleźć listę encji HTML:

Jeśli używasz języka skryptowego po stronie serwera (PHP, ASP.NET itp.), Najprawdopodobniej istnieje funkcja, która zmieniłaby ciąg znaków i przekształciłaby znaki specjalne w encje HTML. W PHP należy użyć „htmlspecialchars ()” lub „htmlentities ()”. Ta ostatnia obejmuje wszystkie znaki HTML.

Jeśli chcesz ładnie wyświetlić kod JavaScript, wypróbuj jeden z podświetlaczy kodu:

Wissam El-Kik
źródło
1

Mam teorię:

  • Zawiń określoną część dokumentu w noscriptznacznik.
  • Użyj funkcji DOM, aby odrzucić wszystkie scripttagi wewnątrz noscripttagu, a następnie rozpakuj jego zawartość.

Przykład dowodu koncepcji:

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
źródło
Jeśli zawartość w div jest niezaufana, co, jak sądzę, jest zadane. Mogą po prostu zamknąć <noscript>tag, a następnie wstrzyknąć to, co chcą.
osłony
Tak, właściwym rozwiązaniem jest naprawienie problemu po stronie serwera. To, co robię za pomocą JavaScript, powinno być lepiej wykonywane po stronie serwera.
Salman A
0

Jeśli chcesz później ponownie włączyć tagi skryptów, moim rozwiązaniem było złamanie środowiska przeglądarki, aby każdy uruchomiony skrypt dość wcześnie zgłaszał błąd. Jednak nie jest to całkowicie niezawodne, więc nie można go używać jako funkcji bezpieczeństwa.

Jeśli spróbujesz uzyskać dostęp do właściwości globalnych, Chrome zgłosi wyjątek.

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

Nadpisuję wszystkie właściwości window, które można nadpisać , ale możesz też rozszerzyć je, aby zepsuć inne funkcje.

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
źródło