Sama data kahdella tavalla
Muuta javascriptissa olevaa
NIMI
-vakiota ja aja uudelleen. Seuraa konsolia.
innerHTML
textContent
Osoitteet
CSS
/* Esikatselualue määrittelee sekä taustan että tekstin värin, jotta pari on varmasti luettava kummassakin teemassa. */ #alue { background-color: #ffffff; color: #1a1a1a; padding: 0.5rem; } @media (prefers-color-scheme: dark) { #alue { background-color: #1e1e1e; color: #f0f0f0; } } #vaarin, #oikein { padding: 0.5rem; border: 1px solid currentColor; margin-bottom: 0.5rem; min-height: 2rem; } #vaarin { border-color: #a00000; } #oikein { border-color: #1a7a1a; }
HTML
<div id="alue"> <h2>Sama data kahdella tavalla</h2> <p>Muuta javascriptissa olevaa <code>NIMI</code>-vakiota ja aja uudelleen. Seuraa konsolia.</p> <h3>innerHTML</h3> <div id="vaarin"></div> <h3>textContent</h3> <div id="oikein"></div> <h3>Osoitteet</h3> <p id="linkit"></p> </div>
Javascript
/* suorita tämä koodi Run-painikkeella */ /* Kuvittele, että tämä tulee lomakkeelta, palvelimelta tai osoiteriviltä. Kokeile vaihtaa arvoksi pelkkä "Matti" ja aja uudelleen. */ const NIMI = `<img src="x" onerror="console.log('XSS! Tässä voisi olla mitä tahansa koodia.')" />`; /* --- VAARALLINEN: data käsitellään merkkauksena --- */ /* innerHTML jäsentää merkkijonon HTML:nä. Huomaa, ettei tämä vaadi script-elementtiä: sellainen ei suorittuisi, mutta tapahtuma-attribuutit suorittuvat. Konsoliin ilmestyy rivi, jota sinne ei pitäisi tulla. */ document.getElementById("vaarin").innerHTML = `<p>Tervetuloa, ${NIMI}!</p>`; /* --- TURVALLINEN: data käsitellään tekstinä --- */ /* textContent asettaa tekstin tekstinä. Merkit < ja > näkyvät sellaisinaan eikä niistä tule elementtejä. */ const kappale = document.createElement("p"); kappale.textContent = `Tervetuloa, ${NIMI}!`; document.getElementById("oikein").replaceChildren(kappale); /* --- Osoitteet ovat oma tapaus --- */ /* Attribuutin asettaminen on yleensä turvallista, mutta osoiteattribuutit eivät ole: javascript:-alkuinen osoite suorittaa koodia klikattaessa. */ const OSOITTEET = [ "https://www.jyu.fi/", "javascript:console.log('myös tämä on XSS')", "ei-osoite-lainkaan" ]; /** * Palauttaa osoitteen vain jos sen protokolla on sallittu. * * @param {string} teksti Tarkistettava osoite * @returns {string|null} Osoite tai null */ const turvallinenOsoite = function (teksti) { try { const osoite = new URL(teksti); if (osoite.protocol === "https:" || osoite.protocol === "http:") { return osoite.href; } } catch (e) { /* URL-konstruktori heittää poikkeuksen kelvottomasta osoitteesta */ } return null; }; const kohde = document.getElementById("linkit"); kohde.replaceChildren(); for (const teksti of OSOITTEET) { const turvallinen = turvallinenOsoite(teksti); console.log(turvallinen === null ? "HYLÄTTY: " + teksti : "HYVÄKSYTTY: " + turvallinen); if (turvallinen === null) { continue; } const linkki = document.createElement("a"); linkki.href = turvallinen; linkki.textContent = turvallinen; linkki.rel = "noopener noreferrer"; kohde.append(linkki, " "); }
Konsoli