IlmHamroh
JavaScript Full-stack/13-qism. Brauzer API'lari, performans va xavfsizlik31/32-dars19 daqiqa
Mundarija (32)

XSS va DOM xavfsizligi: source, sink, sanitizatsiya va Trusted Types

Qisqacha: XSS — begona matn sahifangizda kod bo'lib ishlashi. U uch yo'l bilan keladi: serverda saqlangan (stored), havoladan qaytgan (reflected) va brauzerning o'zida — JavaScript begona qiymatni xavfli joyga yozganda (DOM XSS). Himoya qatlamlari: matnni faqat textContent bilan qo'yish, havolada https: ni tekshirish, HTML kerak bo'lsa DOMPurify yoki setHTML bilan tozalash va brauzer darajasida CSP hamda Trusted Types bilan xavfli joylarni yopish.

Bu darsda

  • Stored, reflected va DOM XSS farqini va har birining yo'lini tushuntira olasiz.
  • Kodda source (begona qiymat manbai) va sink (uni kodga aylantiradigan joy) larni topa olasiz.
  • javascript: havolalardan URL tekshiruvi bilan himoyalanasiz.
  • HTML'ni DOMPurify va setHTML bilan tozalaysiz, Trusted Types bilan xavfli joylarni brauzer darajasida yopasiz.
  • vazifalar da XSS auditini o'tkazib, himoyani ESLint qoidalari bilan qotirasiz.

Oldin bilishingiz kerak: Kontentni o'zgartirish va XSS xavfi, HTML darajasidagi xavfsizlik, Tagged template literals, eval, new Function va with, URL va URLSearchParams.

1. Nega bu kerak?

Kontentni o'zgartirish va XSS xavfi darsida birinchi hujumni ko'rdik: izohdagi <img onerror> innerHTML orqali kodga aylandi. Tagged template literals darsida html tegi bilan ekranlashni, eval darsida esa satrdan kod yasash xavfini ko'rdik. Bu dars ularni bitta tizimga yig'adi.

XSS (Cross-Site Scripting) nima uchun frontenddagi eng xavfli xato? Chunki hujumchining kodi sizning saytingiz nomidan ishlaydi. Brauzer uni "o'ziniki" deb biladi va unga hamma narsani beradi: sahifadagi ma'lumot, localStorage dagi token, HttpOnly bo'lmagan cookie'lar, mehmon nomidan so'rov yuborish, soxta "Qayta kiring" oynasi.

2005 yilda MySpace ijtimoiy tarmog'ida Samy Kamkar profiliga kichik XSS kodi qo'ydi. Profilni ochgan har kim uni "do'st" qilib qo'shar va kodni o'z profiliga ko'chirardi. 20 soatda bir milliondan ortiq profil zararlandi. Bunday o'zini o'zi ko'paytirib tarqaladigan zararli kod qurt (worm) deyiladi — Samy qurti tarixdagi eng tez tarqalganlaridan biri. Hammasi bitta tozalanmagan maydondan boshlandi.

2. XSS'ning uch turi

2.1 Begona kod qayerdan keladi?

Uch turning farqi — zararli matn qayerda "yashaydi" va kim uni sahifaga qo'yadi:

Tur Matn qayerda Kim sahifaga qo'yadi
Stored (saqlangan) Serverdagi bazada (izoh, ism) Server yoki JS — har bir mehmonga
Reflected (qaytgan) Havolada (?q=...) Server javobga qo'shib qaytaradi
DOM XSS Havola, postMessage, xotira Brauzerdagi JavaScript

Stored eng xavflisi: bir marta yozilgan izoh minglab mehmonga zarar beradi. Reflected uchun hujumchi qurbonni havolani bosishga undashi kerak — Telegram guruhga "chegirma" havolasi tashlaydi. DOM XSS esa serverga umuman tegmasligi mumkin: masalan, # dan keyingi qism serverga yuborilmaydi, server uni ko'rmaydi va tekshira olmaydi.

flowchart TD
  H["Hujumchi matni"] --> S["Server bazasi<br/>(stored)"]
  H --> U["Havola ?q=...<br/>(reflected, DOM)"]
  S --> J["Sahifadagi JavaScript"]
  U --> J
  J -->|"innerHTML, href,<br/>eval"| X["Kod ishladi"]
  J -->|"textContent,<br/>tekshirilgan URL"| T["Oddiy matn"]
  classDef xavf fill:#fdeee6,stroke:#b4461a
  classDef xavfsiz fill:#e8f1ec,stroke:#1f6f4a
  class X xavf
  class T xavfsiz

Nimaga qarang: qayerdan kelmasin, oxirgi qaror bitta joyda — JavaScript qiymatni qayerga yozadi.

2.2 Source va sink

Xavfsizlik mutaxassislari buni ikki so'z bilan ifodalaydi:

  • Source (manba) — begona qiymat kodga kiradigan joy: location.search, location.hash, document.referrer, postMessage xabari (Oyna va tablar orasida aloqa), forma maydoni, serverdan kelgan JSON, hatto localStorage (uni oldin kimdir yozgan bo'lishi mumkin).
  • Sink — qiymatni HTML yoki kod sifatida bajaradigan joy. XSS xavfi darsida birinchi uchtasini ko'rgandik, to'liq ro'yxat:
Sink turi Misollar
HTML innerHTML, outerHTML, insertAdjacentHTML, document.write, iframe.srcdoc
JavaScript eval, new Function, setTimeout("satr"), script.textContent, script.src
URL a.href, iframe.src, location.href, form.action — javascript: bilan

DOM XSS — source'dan kelgan qiymat tekshirilmasdan sink'ka tushishi. Audit qilish oson: kodda sink'larni qidirasiz va har biri uchun "bu qiymat qayerdan keldi?" deb so'raysiz.

2.3 Jonli: havoladan kelgan XSS

Sardor qidiruv natijasini ko'rsatadigan kichik sahifa yozdi: havoladagi ?q= qiymatini oladi va "Qidiruv: osh" deb chiqaradi. Natija oynasi o'z manziliga ega emas, shuning uchun havolani maydondan olamiz:

html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  input { font: inherit; width: 100%; box-sizing: border-box; }
  button { font: inherit; padding: 0.4rem 0.8rem; margin: 0.5rem 0; }
</style>
<label for="link">Do'stingiz yuborgan havola:</label>
<input id="link" value="https://example.com/qidiruv?q=osh">
<button id="unsafe" type="button">innerHTML bilan</button>
<button id="safe" type="button">textContent bilan</button>
<p id="result"></p>
<script>
  document.cookie = "sessiya=abc123"; // namunaviy sessiya
  const result = document.getElementById("result");

  function readQuery() {
    const url = new URL(document.getElementById("link").value);
    return url.searchParams.get("q"); // source
  }

  document.getElementById("unsafe").addEventListener("click", () => {
    result.innerHTML = `Qidiruv: <b>${readQuery()}</b>`; // sink
  });

  document.getElementById("safe").addEventListener("click", () => {
    const bold = document.createElement("b");
    bold.textContent = readQuery();
    result.replaceChildren("Qidiruv: ", bold);
  });
</script>

Avval ikkala tugmani bosing — ikkalasi ham "Qidiruv: osh". Endi maydonni tozalab, hujumchi havolasini qo'ying:

text
https://example.com/qidiruv?q=%3Cimg%20src%3Dx%20onerror%3D%22console.log(document.cookie)%22%3E

%3C — < ning URL'dagi ko'rinishi (URL va URLSearchParams darsidagi kodlash). searchParams.get uni ochadi va <img src=x onerror="..."> chiqadi. «innerHTML bilan» ni bosing — konsolga sessiya=abc123 chiqadi: hujumchining kodi cookie'ni o'qidi. Haqiqiy hujumda console.log o'rnida uni hujumchi serveriga yuborish turardi. «textContent bilan» — konsol jim, ekranda esa teg harflar bo'lib turadi.

Biz buni Chrome 154 da sinadik: xavfli tugmada konsolda aynan sessiya=abc123, xavfsizida — hech narsa.

Tekshirib ko'ring: Server ?q= ni umuman o'qimaydi, sahifa statik (GitHub Pages). Bu sahifa XSS'dan xavfsizmi?

Javob

Yo'q. Bu aynan DOM XSS: zararli qiymat havoladan to'g'ridan-to'g'ri brauzerdagi JavaScript'ga keladi va innerHTML ga tushadi. Server ishtirok etmaydi. Statik sayt ham XSS'ga ochiq — agar uning JavaScript'i source'ni sink'ka yozsa.

Yuqoridagi hujum document.cookie ni o'qidi. Cookie'lar JS'dan darsida ko'rgandik: server cookie'ga HttpOnly bayrog'ini qo'ysa, JavaScript uni umuman ko'rmaydi. Sessiya cookie'si HttpOnly bo'lsa, hujumchi uni o'g'irlay olmaydi.

Lekin bu XSS'ni zararsiz qilmaydi. Hujumchining kodi sahifa ichida ishlayapti — u cookie'ni olib ketmasa ham, fetch bilan mehmon nomidan so'rov yubora oladi: brauzer cookie'ni o'zi qo'shadi. Buyurtmani bekor qilish, parolni o'zgartirish, xabar yuborish — hammasi mumkin, faqat mehmon sahifani ochiq turgan paytda.

Shuning uchun HttpOnly — zarar hajmini kamaytiradi, XSS'ni to'xtatmaydi. XSS'ni faqat bitta narsa to'xtatadi: begona matn sink'ka tushmasligi.

4. URL sink'lari: javascript: havolalar

4.1 Ekranlash yetmaydigan joy

Mehmon profilida "Mening Telegramim" havolasi bor. Sardor uni shunday chiqardi:

js
// ❌ havola manzili tekshirilmagan
link.href = profile.website; // source: mehmon yozgan matn
link.textContent = "Mening sahifam";

textContent — xavfsiz. Lekin href ga mehmon javascript:fetch('https://...') yozsa-chi? Havola bosilganda brauzer manzil o'rniga kodni bajaradi. Biz Live Server'dagi kabi oddiy sahifada sinadik: href="javascript:console.log(...)" havolasi bosilganda Chrome 154 kodni bajardi. Bu yerda ekranlash yordam bermaydi: javascript:alert(1) ichida birorta ham < yoki " yo'q.

4.2 Tekshiruv: faqat ruxsat etilgan protokollar

Qoida — oq ro'yxat: faqat https: va http: ga ruxsat, qolgani rad:

js
function isSafeUrl(input) {
  try {
    const url = new URL(input, "https://example.com/");
    return url.protocol === "https:" || url.protocol === "http:";
  } catch {
    return false; // URL umuman o'qilmadi
  }
}

console.log(isSafeUrl("https://t.me/sizning_kanal")); // true
console.log(isSafeUrl("/menyu")); // true
console.log(isSafeUrl("javascript:alert(1)")); // false
console.log(isSafeUrl(" JavaScript:alert(1)")); // false
console.log(isSafeUrl("java\tscript:alert(1)")); // false
console.log(isSafeUrl("data:text/html,<b>salom</b>")); // false

Nega startsWith("javascript:") emas? Hujumchi katta harf, boshida bo'sh joy yoki o'rtada tab yozadi — brauzer baribir ularni javascript: deb tushunadi. new URL esa brauzer bilan bir xil qoidalar bo'yicha o'qiydi: uchala hiyla ham protocol === "javascript:" beradi va rad etiladi. Ikkinchi argument ("https://example.com/") — nisbiy manzillar (/menyu) uchun asos. URL darsida filtr uchun oq ro'yxat qilgan edik — g'oya o'sha.

4.3 target="_blank" va window.opener

Yana bir eski xavf. Havola yangi tabda ochilsa, yangi sahifa window.opener orqali sizning tabingizga yeta olardi va uni, masalan, soxta login sahifasiga almashtirardi. Bu reverse tabnabbing (teskari tab o'g'irlash) deyiladi. Himoya — rel="noopener" (Maxsus havolalar).

Yaxshi xabar: zamonaviy brauzerlarda target="_blank" o'zi noopener ni bildiradi. Biz Chrome 154 da tekshirdik: oddiy target="_blank" havolasida yangi tabdagi window.opener — null. rel="opener" qo'shilgandagina u bor. JavaScript'dan ochganda esa o'zingiz ayting: window.open(url, "_blank", "noopener").

Tekshirib ko'ring: Sardor himoya yozdi: if (url.startsWith("javascript:")) return;. Mehmon havola maydoniga JavaScript:alert(1) (boshida bo'sh joy, katta J bilan) yozdi. Himoya ishlaydimi?

Javob

Yo'q. startsWith aynan shu harflarni qidiradi: bo'sh joy va katta J uni chalg'itadi. Brauzer esa bo'sh joyni tashlab, katta-kichik harfga qaramay, buni baribir javascript: deb tushunadi — kod ishlaydi. Shuning uchun qora ro'yxat ("buni taqiqla") emas, oq ro'yxat ("faqat https: va http: ga ruxsat") va new URL bilan tekshiriladi.

5. HTML kerak bo'lsa: sanitizatsiya

5.1 DOMPurify

Ba'zan HTML'ga ruxsat berish kerak: izohlarda qalin va kursiv, maqola muharriri. Bunda matnni sanitizatsiya (tozalash) qilinadi: xavfsiz teglar qoladi, qolgani olib tashlanadi. Tozalovchini o'zingiz yozmang (HTML darajasidagi xavfsizlik). Brauzer HTML'ni qanday o'qishini (Brauzer HTML'ni qanday tahlil qiladi) to'liq hisobga olish juda qiyin.

Eng ko'p ishlatiladigan kutubxona — DOMPurify (biz 3.4.16 bilan sinadik, 2026-oktabr). Natija oynasida uni jsDelivr'dan ulaymiz:

html
<div id="comment"></div>
<script type="module">
  import DOMPurify
    from "https://cdn.jsdelivr.net/npm/dompurify@3.4.16/+esm";

  const dirty = '<b>Osh</b> zo\'r! '
    + '<img src="x" onerror="console.log(1)">'
    + '<a href="javascript:alert(1)">bos</a>';

  console.log(DOMPurify.sanitize(dirty));
  console.log(DOMPurify.sanitize(dirty, {
    ALLOWED_TAGS: ["b", "i"],
    ALLOWED_ATTR: [],
  }));
</script>

Konsolda:

text
<b>Osh</b> zo'r! <img src="x"><a>bos</a>
<b>Osh</b> zo'r! bos

Sukut bo'yicha DOMPurify xavfsiz teglarni qoldiradi, lekin xavfli qismlarni olib tashlaydi: onerror atributi va javascript: manzil yo'q. Ikkinchi chaqiruvda biz faqat b va i ga ruxsat berdik — rasm va havola butunlay ketdi, matni ("bos") qoldi. Amalda ro'yxatni imkon qadar tor qiling: izoh uchun 3–4 teg yetadi.

5.2 setHTML: brauzerning o'z tozalovchisi

XSS xavfi darsida setHTML bilan tanishgan edik. Uning ham sozlamasi bor — ruxsat etilgan teglar ro'yxati:

html
<div id="comment"></div>
<script>
  const dirty = '<b>Osh</b> zo\'r! '
    + '<img src="x" onerror="console.log(1)">'
    + '<a href="javascript:alert(1)">bos</a>';
  const box = document.getElementById("comment");

  box.setHTML(dirty);
  console.log(box.innerHTML);

  box.setHTML(dirty, {
    sanitizer: { elements: ["b", "a"], attributes: ["href"] },
  });
  console.log(box.innerHTML);
</script>

Konsolda:

text
<b>Osh</b> zo'r! <a>bos</a>
<b>Osh</b> zo'r! <a>bos</a>

Ikkinchi chaqiruvda biz a va href ga ruxsat berdik — lekin javascript: manzil baribir olib tashlandi. setHTML xavfli narsani hech qanday sozlama bilan o'tkazmaydi; buning uchun nomi "Unsafe" bilan tugaydigan alohida metod (setHTMLUnsafe) bor. Chrome 154 dagi natija. Qo'llash: Chrome va Edge 146+, Firefox 148+, Safari'da yo'q (web-features, 2026-oktabr). Shuning uchun hozircha amalda DOMPurify ko'proq.

Diqqat: Tozalangan HTML'ni keyin o'zgartirmang. "Tozaladim, endi replace bilan ozgina tahrirlayman" — xavfli: o'zgartirilgan satr yana xavfli bo'lib qolishi mumkin (bunday hujumlar mXSS — mutation XSS deyiladi). Tozalash — sink'dan oldingi oxirgi qadam.

6. Brauzer darajasidagi himoya: CSP va Trusted Types

6.1 CSP: inline skriptni to'xtatish

Kodda xato bo'lishi mumkin — dasturchilar ham odam. Shuning uchun ikkinchi qatlam kerak: brauzerning o'zi xavfli narsani taqiqlasin. eval darsida CSP (Content Security Policy) bilan tanishgan edik: u 'unsafe-eval' siz eval ni to'xtatadi. XSS'ga qarshi uning asosiy kuchi — script-src: HTML ichiga yozilgan skriptlar va onerror kabi atributlar ishlamaydi.

O'zimizniki bo'lgan inline skript-chi? Unga nonce ("bir martalik raqam") beriladi: server har javobda yangi tasodifiy satr yasaydi, uni CSP'ga va o'z <script> lariga yozadi. Hujumchi bu raqamni oldindan bilmaydi:

html
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Security-Policy"
        content="script-src 'nonce-r4nd0m'; object-src 'none'">
</head>
<body>
  <p id="out"></p>
  <script nonce="r4nd0m">
    document.addEventListener("securitypolicyviolation", (e) => {
      console.log(`To'sildi: ${e.violatedDirective}`);
    });
    console.log("1: nonce bor skript ishladi");
  </script>
  <script>
    console.log("2: nonce'siz skript ishladi");
  </script>
  <script nonce="r4nd0m">
    document.getElementById("out").innerHTML =
      '<img src="x" onerror="console.log(\'3: XSS\')">';
  </script>
</body>
</html>

Konsolda:

text
1: nonce bor skript ishladi
To'sildi: script-src-elem
To'sildi: script-src-attr

Nonce'li skript ishladi. Nonce'siz — to'sildi (script-src-elem — <script> elementlari qoidasi). Uchinchi skriptda innerHTML xato — lekin hujum baribir ishlamadi: onerror atributi to'sildi (script-src-attr). securitypolicyviolation hodisasi har bir to'sishni xabar qiladi. Chrome konsolida esa to'liq xabar chiqadi:

text
Executing inline event handler violates the following Content Security Policy directive 'script-src 'nonce-r4nd0m''. Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present. The action has been blocked.

Tarjimasi: "HTML ichidagi hodisa ishlovchisini bajarish CSP qoidasini buzadi. Ruxsat uchun 'unsafe-inline', hash yoki nonce kerak. Hash'lar hodisa ishlovchilari, style atributlari va javascript: o'tishlarga qo'llanmaydi. Amal bloklandi." Nonce'siz <script> uchun Chrome xabarda shu skriptning tayyor hash'ini ham beradi — o'zgarmaydigan skriptga nonce o'rniga hash yozish mumkin.

Bu yerda nonce — darsdagi qattiq satr, chunki sahifa statik. Haqiqiy saytda u server tomonidan har javobda yangilanadi (server — keyingi qismlarda). CSP'ning qolgan direktivalari va saytga qanday qo'yilishi — keyingi darsda.

6.2 Trusted Types: sink'larni qulflash

CSP skriptni to'xtatadi, lekin innerHTML ga begona satr yozishning o'zini emas. Trusted Types ("ishonchli turlar") — bundan ham qattiqroq qoida: xavfli sink'lar oddiy satrni qabul qilmaydi. Ularga faqat siz yozgan siyosat (policy) dan o'tgan maxsus qiymat beriladi.

U CSP direktivasi bilan yoqiladi:

text
require-trusted-types-for 'script'; trusted-types bahor

Birinchi qism — "sink'larga satr bermang", ikkinchisi — "faqat bahor nomli siyosat yaratish mumkin". Sinab ko'ramiz:

html
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Security-Policy"
        content="require-trusted-types-for 'script';
                 trusted-types bahor">
</head>
<body>
<p id="out"></p>
<script>
  const out = document.getElementById("out");
  const comment = '<img src="x" onerror="console.log(\'XSS\')">Zo\'r';

  try {
    out.innerHTML = comment; // oddiy satr
  } catch (error) {
    console.log(error.message);
  }

  const policy = trustedTypes.createPolicy("bahor", {
    createHTML: (input) => input
      .replaceAll("&", "&amp;")
      .replaceAll("<", "&lt;")
      .replaceAll(">", "&gt;"),
  });
  out.innerHTML = policy.createHTML(comment);
  console.log(out.innerHTML);

  try {
    setTimeout("console.log('satrdan kod')");
  } catch (error) {
    console.log(error.message);
  }
</script>
</body>
</html>

Konsolda:

text
Failed to set the 'innerHTML' property on 'Element': This document requires 'TrustedHTML' assignment.
&lt;img src="x" onerror="console.log('XSS')"&gt;Zo'r
Failed to execute 'setTimeout' on 'Window': This document requires 'TrustedScript' assignment.

Tarjimasi: "innerHTML ni o'rnatib bo'lmadi: bu hujjat TrustedHTML talab qiladi." Oddiy satr — TypeError. Siyosatdan o'tgan qiymat esa qabul qilindi: bizning createHTML uni ekranladi va teg harflar bo'lib turibdi. Satrli setTimeout ham to'xtadi (TrustedScript kerak). Ko'ryapsizmi, endi xavfli joylarning hammasi bitta joyga — siyosat funksiyasiga — yig'ildi. Audit qilish uchun butun kodni emas, faqat siyosatlarni o'qish yetadi.

Siyosat ichida o'zingiz ekranlashingiz shart emas: DOMPurify RETURN_TRUSTED_TYPE: true sozlamasi bilan tayyor TrustedHTML qaytaradi. U o'z siyosatini dompurify nomi bilan yaratadi — CSP'da trusted-types dompurify bahor deb ruxsat berish kerak (biz Chrome 154 da shunday sinadik). setHTML esa Trusted Types talab qilmaydi — u o'zi xavfsiz sink.

Qo'llash: Chrome va Edge 83+, Firefox 148+, Safari 26+ — 2026 yil fevralidan Baseline (web-features). Mavjud katta loyihaga birdan yoqish qiyin: avval Content-Security-Policy-Report-Only sarlavhasi bilan "faqat xabar ber" rejimida qaysi joylar buzilishini yig'ishadi, keyin tuzatib, yoqishadi.

Tekshirib ko'ring: Sahifada Trusted Types yoqilgan. Sardor el.textContent = comment yozdi. Xato chiqadimi?

Javob

Yo'q. textContent — sink emas: u matnni HTML deb o'qimaydi. Trusted Types faqat xavfli joylarni (innerHTML, script.src, eval…) qulflaydi. Xavfsiz API'lar avvalgidek ishlaydi — shu sababli textContent va createElement bilan yozilgan kod Trusted Types'ga hech o'zgartirishsiz o'tadi.

Hujumchi nigohi

Hujumchi vazifalar ilovasidagi har bir kirish nuqtasini sinaydi:

  • Vazifa matni: <img src=x onerror=alert(1)> — ro'yxatda, aria-label da va o'chirish dialogida chiqadi.
  • JSON import: "><svg onload=alert(2)> #<b>teg</b> #xss — tegga aylanadi deb umid qiladi.
  • Havola: ?filtr=%3Cscript%3E… — filtr nomi sahifaga chiqadi deb o'ylaydi.

Hammasi muvaffaqiyatsiz, chunki ilovada birorta ham sink yo'q: matn faqat textContent bilan, filtr — oq ro'yxat bilan (URL darsi), HTML'ni hech qayerda yasamaymiz. Kanon tekshiruvi buni brauzerda isbotlaydi: sahifada img va svg soni 0, birorta alert ham chiqmadi. Keyingi darsda buning ustiga CSP va Trusted Types qo'shiladi — kimdir kelajakda innerHTML yozib qo'ysa ham, brauzer uni to'xtatadi.

7. Ko'p uchraydigan xatolar

7.1 Xavfli so'zlarni o'chirish

text.replaceAll("<script>", "") — himoya emas. <img onerror>, <svg onload>, katta harflar, <scr<script>ipt> — chetlab o'tish yo'llari cheksiz. Tuzatish: textContent yoki ishonchli tozalovchi.

7.2 Noto'g'ri joyda ekranlash

HTML ekranlash faqat qiymat matn yoki qo'shtirnoqli atribut ichiga tushganda ishlaydi. Uch joyda u yetmaydi (Tagged template literals darsida birinchi marta ko'rgan edik):

  • href va src — javascript:alert(1) da ekranlanadigan belgi yo'q.
  • Qo'shtirnoqsiz atribut — <td class=${sinf}> da bo'sh joy yangi atribut ochadi: x onclick=….
  • <script> va <style> ichi — u yerda HTML emas, JavaScript va CSS qoidalari ishlaydi.

Tuzatish: har joyning o'z qoidasi — matn uchun textContent, atribut uchun setAttribute yoki xususiyat (el.title = …), URL uchun protokol tekshiruvi. <script> ichiga begona qiymatni umuman qo'ymang.

7.3 "Bu ma'lumot o'zimizning serverdan"

Serverdagi ma'lumotni mehmonlar yozgan. Stored XSS aynan shu ishonchdan boshlanadi. Tuzatish: begona matn — qayerdan kelmasin — begona.

7.4 Tozalangan HTML'ni keyin o'zgartirish

mXSS xavfi. Tuzatish: tozalash — sink'dan oldingi oxirgi qadam.

7.5 Tozalash uchun innerHTML = ""

Bo'sh satr xavfsiz, lekin kodda innerHTML qolsa, keyin kimdir unga o'zgaruvchi qo'yib yuboradi va ESLint qoidasi ham bu ikkisini farqlay olmaydi. Tuzatish: element ichini tozalash uchun el.replaceChildren().

8. Mashqlar

1-mashq (oson): Sink'larni toping

Quyidagi qatorlardan qaysilari sink va qaysilari xavfsiz?

js
card.textContent = taom.nom;              // A
card.innerHTML = `<b>${taom.nom}</b>`;    // B
link.href = mehmon.sayt;                  // C
setTimeout(yangila, 1000);                // D
setTimeout("yangila()", 1000);            // E
img.alt = mehmon.ism;                     // F

Sink'lar:

Yechim
  • B — HTML sink: taom.nom ichida teg bo'lsa, u HTML bo'ladi.
  • C — URL sink: javascript: manzil bo'lishi mumkin; protokolni tekshirish kerak.
  • E — JavaScript sink: satr kod sifatida bajariladi (eval darsi).
  • A, D, F — xavfsiz: textContent va alt — oddiy matn, setTimeout(yangila) — funksiya, satr emas.

2-mashq (o'rta): Xavfsiz profil havolasi

renderLink(website) funksiyasini yozing: <a> element qaytarsin. Manzil xavfsiz bo'lsa (isSafeUrl — «Tekshiruv: faqat ruxsat etilgan protokollar» bo'limidan) — href o'rnatilsin, matni — manzilning hostname i. Xavfsiz bo'lmasa — havola emas, span va "Noto'g'ri havola" matni.

Yechim
html
<ul id="links"></ul>
<script>
  function isSafeUrl(input) {
    try {
      const url = new URL(input, "https://example.com/");
      return url.protocol === "https:" || url.protocol === "http:";
    } catch {
      return false;
    }
  }

  function renderLink(website) {
    if (!isSafeUrl(website)) {
      const span = document.createElement("span");
      span.textContent = "Noto'g'ri havola";
      return span;
    }
    const link = document.createElement("a");
    link.href = website;
    const base = "https://example.com/";
    link.textContent = new URL(website, base).hostname;
    link.rel = "noopener";
    return link;
  }

  const list = document.getElementById("links");
  for (const site of ["https://t.me/sizning_kanal",
    "javascript:alert(1)"]) {
    const li = document.createElement("li");
    li.append(renderLink(site));
    list.append(li);
    console.log(li.textContent);
  }
</script>

Konsolda:

text
t.me
Noto'g'ri havola

Xavfli manzil href ga umuman tushmadi. rel="noopener" — target="_blank" qo'shilsa ham himoya joyida tursin.

3-mashq (qiyin): DOMPurify + Trusted Types

Trusted Types yoqilgan sahifada izohni HTML sifatida chiqaring: qalin va kursivga ruxsat, qolgani tozalansin. Siyosat nomi — bahor, uning createHTML i DOMPurify'ni chaqirsin (ALLOWED_TAGS: ["b", "i"]).

Ishora: DOMPurify o'z siyosatini ham yaratadi — CSP'dagi ro'yxatga dompurify ni qo'shing.

Yechim
html
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Security-Policy"
        content="require-trusted-types-for 'script';
                 trusted-types dompurify bahor">
</head>
<body>
<div id="comment"></div>
<script type="module">
  import DOMPurify
    from "https://cdn.jsdelivr.net/npm/dompurify@3.4.16/+esm";

  const policy = trustedTypes.createPolicy("bahor", {
    createHTML: (input) => DOMPurify.sanitize(input, {
      ALLOWED_TAGS: ["b", "i"],
      ALLOWED_ATTR: [],
    }),
  });

  const dirty = '<b>Zo\'r!</b> <i>tavsiya</i>'
    + '<img src=x onerror="console.log(\'XSS\')">';
  const box = document.getElementById("comment");
  box.innerHTML = policy.createHTML(dirty);
  console.log(box.innerHTML);
</script>
</body>
</html>

Konsolda:

text
<b>Zo'r!</b> <i>tavsiya</i>

Endi sahifadagi har bir innerHTML majburan bahor siyosatidan o'tadi. Kimdir ertaga box.innerHTML = dirty yozsa — TypeError, va xato darhol ko'rinadi.

4-mashq: Vazifalar qadami — XSS auditi

vazifalar foydalanuvchi matnini ko'rsatadi: vazifalar, teglar, import qilingan JSON, URL'dagi filtr. Bu qadamda kod o'zgarmaydi — avval audit qilamiz, keyin himoyani ESLint bilan qotiramiz.

1. Branch va audit.

bash
git switch -c chore/xss-audit
grep -rnE \
  -e "innerHTML|outerHTML|insertAdjacentHTML|document\.write" \
  -e "eval\(|new Function|javascript:|srcdoc|setAttribute\(" \
  assets/js sw.js

Natija: sink — 0. Topilganlar tekshirildi:

  • setAttribute — faqat aria-* atributlar uchun. Foydalanuvchi matni aria-label da — bu atribut qiymati, u bajarilmaydi.
  • havola.href — o'zimiz yasagan blob: manzil (eksport fayli, File va Blob darsidagi qadam).
  • querySelector ichidagi ${…} — raqamli id va ichki amal nomi, foydalanuvchi matni emas.
  • URL'dagi filtr — oq ro'yxat (11-qism).
  • vendor/web-vitals.js da ham sink yo'q. target="_blank" havola yo'q.

2. Himoyani qotirish — eslint.config.js ga yangi blok (assets/js va sw.js uchun):

js
{
  // XSS himoyasi (13/#31): matnni HTML yoki kod qilib
  // bajaradigan joylar (sink) taqiqlanadi.
  // Foydalanuvchi matni — faqat textContent
  files: ["assets/js/**/*.js", "sw.js"],
  rules: {
    "no-eval": "error",
    "no-implied-eval": "error", // setTimeout("kod")
    "no-new-func": "error",
    "no-script-url": "error", // "javascript:..."
    "no-restricted-properties": [
      "error",
      {
        object: "document",
        property: "write",
        message: "XSS: textContent ishlating.",
      },
      {
        property: "insertAdjacentHTML",
        message: "XSS: insertAdjacentElement yoki append ishlating.",
      },
    ],
    "no-restricted-syntax": [
      "error",
      {
        selector: "AssignmentExpression > MemberExpression.left"
          + "[property.name=/^(innerHTML|outerHTML)$/]",
        message: "XSS: innerHTML/outerHTML o'rniga "
          + "textContent yoki createElement.",
      },
    ],
  },
},

(Kanonda selector, message va izoh qatorlari uzunroq; telefon ekraniga sig'ishi uchun bu yerda bo'lindi.)

Birinchi to'rttasi — ESLint'ning o'z qoidalari (ESLint va Prettier). no-restricted-properties — ma'lum xususiyatni taqiqlaydi. no-restricted-syntax eng kuchlisi: kodning daraxtidan (AST, JS dvigateli ichida) "innerHTML ga tayinlash" shaklini qidiradi. O'qish (el.innerHTML ni konsolga chiqarish) taqiqlanmaydi — faqat yozish.

Qoidalar ishlashini sinov fayli bilan tekshirdik: el.innerHTML = …, insertAdjacentHTML, document.write, setTimeout("alert(1)") → ✖ 4 problems (4 errors).

3. Isbot — manfiy nazorat. Himoya ishlayaptimi? Buni bilishning yo'li — uni ataylab buzish. Ilovaning Web Vitals qadamidagi nusxasida yorliq.textContent ni yorliq.innerHTML ga almashtirdik va keshga <img src=x onerror=alert(1)> matnli vazifa qo'ydik:

text
buzilgan: label: ""                              img: 1  alert: ["1"]
kanon:    label: "<img src=x onerror=alert(1)>"  img: 0  alert: []
eslint (buzilgan render.js): 75:3 error XSS: innerHTML/outerHTML o'rniga textContent yoki createElement  no-restricted-syntax

Buzilgan versiyada hujum ishladi (alert chiqdi), kanonda — matn harflar bo'lib turdi. ESLint esa buzilgan qatorni darhol topdi: endi innerHTML qaytib kirsa, npm run lint yiqiladi.

4. Brauzer tekshiruvi — 111/111 (+8 XSS tekshiruvi, keyingi har qadamda ham qayta ishlaydi): qo'shilgan vazifa, aria-label, JSON import ("><svg onload=alert(2)> #<b>teg</b> #xss), teglar (Teglar: #xss — 1), dialog matni, ?filtr=%3Cscript%3E… — hammasi matn; img/svg — 0, alert — 0. Testlar 90/90.

5. Commit va PR. Sarlavha 72 belgidan oshmasin, tafsilot — tanada:

bash
git add eslint.config.js
git commit \
  -m "chore: XSS auditi — xavfli sink'lar ESLint bilan taqiqlandi" \
  -m "Foydalanuvchi matni faqat textContent orqali."
gh pr create --fill
gh pr merge --merge

Diff: 1 fayl, +32. Kod o'zgarmadi — lekin endi u o'zgarishdan himoyalangan.

9. Real ishda

  • Freymvorklar. React matnni sukut bo'yicha ekranlaydi; xavfli joy — dangerouslySetInnerHTML (nomi ataylab qo'rqinchli). Lit — html tegi bilan (Tagged template literals). React'dagi XSS tuzoqlari — 18-qismda.
  • Trusted Types amalda. Uni Google jamoasi ishlab chiqqan va o'z xizmatlarida qo'llaydi. 2026 yildan u barcha asosiy brauzerlarda bor — yangi loyihada boshidan yoqish oson.
  • Bug bounty ("xato uchun mukofot"). Ko'p kompaniyalar o'z saytidan zaiflik topib, xabar bergan odamga pul to'laydi. XSS — shunday topiladigan eng ko'p zaifliklardan biri.
  • Intervyu. "XSS turlari?", "Source va sink nima?", "innerHTML o'rniga nima?", "CSP va Trusted Types qanday himoya qiladi?" — frontend xavfsizlik savollarining asosi.

Xulosa

  • XSS — begona matn sizning saytingizda kod bo'lib ishlashi; stored, reflected va DOM turlari bor, oxirgi qaror doim JavaScript'ning sink'ida.
  • Source → sink: begona qiymat innerHTML, eval, href kabi joylarga tekshirilmasdan tushmasin.
  • Matn — textContent; URL — new URL bilan protokol oq ro'yxati; HTML — DOMPurify yoki setHTML, tozalash — oxirgi qadam.
  • HttpOnly cookie'ni himoya qiladi, lekin XSS'ni emas; target="_blank" zamonaviy brauzerda o'zi noopener.
  • CSP (script-src, nonce) inline kodni, Trusted Types esa sink'larga oddiy satr berishni to'xtatadi.

Keyingi dars: Brauzer xavfsizlik modeli — same-origin, CORS, CSP'ning to'liq tuzilmasi, COOP/COEP, sandbox va Permissions-Policy; vazifalar ga CSP va Trusted Types qo'yamiz.

Manbalar

  • OWASP: "Cross Site Scripting Prevention Cheat Sheet", "DOM based XSS Prevention Cheat Sheet" — cheatsheetseries.owasp.org
  • web.dev: "Prevent DOM-based cross-site scripting vulnerabilities with Trusted Types" — web.dev/articles/trusted-types
  • DOMPurify README (3.4.16) — github.com/cure53/DOMPurify
  • MDN: Element.setHTML(), Trusted Types API, CSP script-src — developer.mozilla.org
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
XSS va DOM xavfsizligi: source, sink, sanitizatsiya va Trusted Types — IlmHamroh