JavaScript · bezpieczne wyświetlanie

textContent i innerHTML — bezpieczne wyświetlanie danych na stronie

textContent kontra innerHTML, nieszkodliwy przykład XSS przez onerror, createElement i zamiana znaków specjalnych na encje. Trzy zadania z pomiarami.

45 minutPoziom podstawowyPliki i rozwiązania

Materiał redakcyjny TablicaPro · liceum i technikum

const li = document.createElement("li");
li.textContent = tekst;
lista.append(li);
textContent: 0 elementów.
innerHTML: 1 element b.

Wyświetlenie tekstu wpisanego przez użytkownika wydaje się niewinne. Ale jeśli wstawimy go na stronę jako HTML, każdy znacznik w nim zostanie zinterpretowany — także taki, który uruchamia kod. To klasyczny atak XSS. Różnica między textContent i innerHTML to różnica między „pokaż jako tekst” a „zinterpretuj jako kod strony”. Na lekcji uczniowie sprawdzają ją na nieszkodliwych przykładach i uczą się bezpiecznego wstawiania danych.

Scenariusz jest przeznaczony na 45 minut dla klasy znającej DOM i zdarzenia — na przykład po lekcjach o DOM w JavaScript i zdarzeniach. Podobny temat po stronie PHP omawia lekcja o htmlspecialchars. Ćwiczenia wykorzystują nieszkodliwy ładunek, który tylko ustawia zmienną.

Przygotowanie stanowisk

Potrzebna jest przeglądarka z konsolą (F12) i edytor tekstu. Rozpakuj pakiet z sekcji materiałów do osobnego folderu. HTML (index.html) jest gotowy i taki sam w obu folderach — pracujesz w start/app.js. Po zapisaniu app.js odśwież stronę. Błędy pokazuje konsola. Gotowe rozwiązanie jest w folderze rozwiazanie. Serwer i Node.js nie są wymagane.

Cele i plan lekcji

Po zajęciach uczeń potrafi:

  • wstawić dane na stronę właściwością textContent i wyjaśnić, że znaczniki są wtedy tylko tekstem;
  • wykazać, że innerHTML interpretuje znaczniki i wykonuje atrybuty zdarzeń, np. onerror;
  • bezpiecznie dodać element do listy (createElement, textContent, append);
  • zabezpieczyć tekst przez zamianę znaków specjalnych na encje HTML.
CzasPraca na lekcji
0–5 minProblem: wpis w formie <b>Ola</b>
5–15 mintextContent kontra innerHTML
15–22 minXSS przez onerror — nieszkodliwy przykład
22–37 minTrzy zadania i pomiary
37–42 minZasady bezpiecznego wstawiania
42–45 minPytania kontrolne

Dwie właściwości, dwa zachowania

  • element.textContent = tekst wstawia tekst dosłownie. Wpis <b>Ola</b> zostanie wyświetlony jako sześć znaków <, b, > itd. i nie utworzy żadnego elementu.
  • element.innerHTML = tekst traktuje tekst jako kod HTML. Wpis <b>Ola</b> utworzy element b z tekstem „Ola” wewnątrz.

To użyteczne, gdy sami piszemy fragment strony, ale niebezpieczne dla danych pochodzących od użytkownika, z adresu strony lub z serwera.

Dlaczego to groźne

Wpis <img src=x onerror="...kod..."> wstawiony przez innerHTML utworzy obraz, którego adres jest błędny, więc przeglądarka wykona kod z atrybutu onerror. Atakujący mógłby w ten sposób odczytać dane strony lub podszyć się pod użytkownika. W lekcji ładunek tylko ustawia zmienną window.zdarzenie, a skutek sprawdzamy w konsoli: po innerHTML zmienna istnieje, po textContent — nie. Skrypty wstawione przez innerHTML w znaczniku <script> same się nie uruchamiają, ale atrybuty zdarzeń (onerror, onload, onclick) — tak.

Jak wstawiać bezpiecznie

  1. Domyślnie używaj textContent.
  2. Nowe elementy twórz przez document.createElement, a ich tekst ustawiaj przez textContent, po czym dodawaj je append.
  3. Gdy musisz użyć innerHTML, wstawiaj tylko własne, stałe fragmenty — a dane użytkownika wcześniej zamieniaj na encje (& → &amp;, < → &lt;, > → &gt;, " → &quot;, ' → &#39;).
  4. Po stronie serwera dane dodatkowo zabezpieczaj przy wypisywaniu (w PHP funkcją htmlspecialchars).

Zadania

Zadanie 1. textContent. Napisz przezTextContent(tekst) i wstaw do #wynik wpis <b>Ola</b>. Sprawdź, czy powstał element b (wynik.children.length).

Zadanie 2. innerHTML. Napisz przezInnerHTML(tekst) i wstaw ten sam wpis. Porównaj liczbę elementów w #wynik. Następnie wstaw nieszkodliwy ładunek <img src=x onerror="window.zdarzenie = 1"> przez oba sposoby i sprawdź w konsoli wartość window.zdarzenie.

Zadanie 3. Bezpieczna lista i encje. Napisz dodajDoListy(tekst) (createElement, textContent, append) i dodaj ten ładunek do listy. Napisz zabezpiecz(tekst) i sprawdź wynik dla <b>"O'Reilly" & Co</b>.

Dla chętnych: sprawdź, co zrobi wstawienie <script>window.zdarzenie = 2</script> przez innerHTML i wyjaśnij różnicę wobec onerror.

Rozwiązanie i wyniki

const pole = document.querySelector("#pole");
const wynik = document.querySelector("#wynik");
const lista = document.querySelector("#lista");

// Zadanie 1: wstawianie tekstu
function przezTextContent(tekst) {
  wynik.textContent = tekst;
}

// Zadanie 2: wstawianie jako HTML (niebezpieczne dla danych od uzytkownika)
function przezInnerHTML(tekst) {
  wynik.innerHTML = tekst;
}

// Zadanie 3: bezpieczne dodawanie elementu listy i zamiana znakow specjalnych
function dodajDoListy(tekst) {
  const li = document.createElement("li");
  li.textContent = tekst;
  lista.append(li);
}

function zabezpiecz(tekst) {
  return tekst
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#39;");
}

document.querySelector("#a").addEventListener("click", () => przezTextContent(pole.value));
document.querySelector("#b").addEventListener("click", () => przezInnerHTML(pole.value));
document.querySelector("#c").addEventListener("click", () => dodajDoListy(pole.value));

Wyniki sprawdzone w przeglądarce: po przezTextContent("<b>Ola</b>") w #wynik jest tekst <b>Ola</b> i 0 elementów potomnych; po przezInnerHTML("<b>Ola</b>") powstaje 1 element b z tekstem „Ola”. Ładunek <img src=x onerror="window.zdarzenie = 1">: po textContent zmienna window.zdarzenie pozostaje niezdefiniowana, po innerHTML jest równa 1. Dodany do listy przez dodajDoListy ładunek jest pokazany jako tekst (element li bez elementu img w środku). Funkcja zabezpiecz zamienia <b>"O'Reilly" & Co</b> na &lt;b&gt;&quot;O&#39;Reilly&quot; &amp; Co&lt;/b&gt;.

Typowe błędy

ObjawCo sprawdzić
Znaczniki widoczne na stronie jako tekstUżyto textContent; jeśli chcesz interpretować HTML, to własny, stały fragment można wstawić innerHTML.
Tekst użytkownika psuje układ stronyWstawiono go przez innerHTML bez zabezpieczenia.
Skrypt w <script> wstawiony przez innerHTML się nie uruchomiłTo zamierzone; atrybuty zdarzeń (onerror) jednak działają — nie polegaj na tym jako zabezpieczeniu.
Zabezpieczenie znaków nie działaZamianę & należy wykonać jako pierwszą; inaczej encje zostaną zamienione drugi raz.
replaceAll nie jest dostępneStarsza przeglądarka; użyj replace z wyrażeniem regularnym z flagą g.
Wstawianie wielu elementów przez += na innerHTML jest wolne i niebezpieczneTwórz elementy przez createElement i dodawaj append.

Pytania kontrolne i punktacja

  1. Czym różni się textContent od innerHTML?
  2. Dlaczego <img src=x onerror="..."> jest groźny po wstawieniu przez innerHTML?
  3. W jakiej kolejności zamieniamy znaki specjalne i dlaczego & jako pierwszy?

Odpowiedzi: textContent wstawia tekst dosłownie, innerHTML interpretuje go jako kod HTML. Bo błędny adres obrazu wywołuje zdarzenie onerror i przeglądarka wykonuje kod z atrybutu. & zamieniamy pierwszy, bo inaczej zamieniłby się także znak & w już utworzonych encjach (&lt; stałoby się &amp;lt;).

Proponowana punktacja (razem 10 punktów): zadanie 1 — przezTextContent 1 pkt, pomiar liczby elementów 1 pkt; zadanie 2 — przezInnerHTML 1 pkt, test ładunku w obu przypadkach 2 pkt; zadanie 3 — dodajDoListy 2 pkt, zabezpiecz 2 pkt; wyjaśnienie XSS 1 pkt. Zadanie dla chętnych nie jest wymagane do pełnej oceny.

Materiały do pobrania

Rozpakuj ZIP przed otwarciem plików. Materiały są dostępne bez logowania; publiczne rozwiązanie służy nauce i samodzielnemu sprawdzeniu, a nie poufnemu sprawdzianowi.

Co dalej?

Następny krok to zabezpieczenia po stronie serwera, nagłówki CSP i obsługa danych użytkownika w aplikacjach. Ten materiał rozwija dział o bezpieczeństwie w kursie JavaScript w TablicaPro, gdzie znajdziesz prezentacje, ćwiczenia i testy do prowadzenia lekcji.

Źródła techniczne: textContent w MDN oraz innerHTML i bezpieczeństwo. Zobacz też lekcję o htmlspecialchars.

Poprowadź kolejne lekcje JavaScript

Pełny kurs: prezentacje, ćwiczenia i testy do pracy z klasą.

Kup dostęp do kursu JavaScript →

Zobacz program kursu