Web xavfsizligi va autentifikatsiya

XSS va CSRF

XSS va CSRF โ€” web ilovalarga eng ko'p tahdid soladigan ikki hujum turi. Ular bir-biriga o'xshab ketadi, lekin butunlay boshqacha ishlaydi. Ushbu darsda biz har birini faqat himoya nuqtai nazaridan tushunamiz: zaiflik qanday paydo bo'ladi va undan qanday himoyalanish kerak.

XSS nima?

XSS (Cross-Site Scripting) โ€” bu hujumchi sizning sahifangizga zararli JavaScript kod "joylashtira" olishi natijasida yuzaga keladigan zaiflik. Bu ko'pincha shunday sodir bo'ladi: ilova foydalanuvchi kiritgan matnni HTML sifatida, uni tozalamasdan sahifaga chiqaradi.

Masalan, izohlar bo'limi foydalanuvchi yozgan matnni to'g'ridan-to'g'ri HTML'ga qo'ysa va foydalanuvchi matn o'rniga <script> tegi yozsa, brauzer uni oddiy matn emas, bajariladigan kod deb qabul qiladi. Natijada boshqa foydalanuvchilar sahifani ochganda o'sha kod ularning brauzerida ishlaydi.

Nima uchun bu xavfli? Chunki zararli skript foydalanuvchining brauzerida, sizning saytingiz nomidan ishlaydi:

XSS'ning uch asosiy turi bor: saqlangan (zararli matn bazaga saqlanib, keyin boshqalarga ko'rsatiladi), aks etuvchi (URL orqali kelib, javobga aks etadi) va DOM asosidagi (mijoz tomonidagi JS xavfsiz ishlov bermaganda). Himoya tamoyillari uchalasi uchun ham deyarli bir xil.

XSS himoyasi 1: chiqishni eskeyplash

Eng asosiy himoya: foydalanuvchi ma'lumotini sahifaga chiqarishda uni HTML sifatida emas, oddiy matn sifatida ko'rsatish. Buning uchun maxsus HTML belgilarini "qochirish" (escaping) kerak:

// Foydalanuvchi kiritmasidagi xavfli belgilarni eskeyplash
function htmlEskeyp(matn) {
  return String(matn)
    .replace(/&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/"/g, '"')
    .replace(/'/g, ''');
}

// Endi foydalanuvchi '<script>...' yozsa ham,
// u matn sifatida ko'rinadi, kod sifatida BAJARILMAYDI.
const xavfsiz = htmlEskeyp(foydalanuvchiIzohi);

Amaliyotda ko'pchilik shablon tizimlari (masalan, EJS, Pug, Handlebars) o'zgaruvchilarni avtomatik eskeyplaydi. Muhimi: avtomatik eskeyplashni o'chirmaslik. Masalan, EJS'da <%= x %> eskeyplaydi, <%- x %> esa eskeyplamaydi โ€” ikkinchisi xavfli:

// EJS shablonida:
// XAVFSIZ (avtomatik eskeyplanadi):
// <%= foydalanuvchi.ismi %>
//
// XAVFLI (eskeyplanmaydi, xom HTML chiqadi):
// <%- foydalanuvchi.ismi %>   <-- foydalanuvchi ma'lumotiga BUNI ishlatmang!
Eskeyplashni chiqish vaqtida (ma'lumotni ko'rsatayotganda) qiling, kontekstga qarab. HTML matnida, HTML atributida, JavaScript ichida va URL'da eskeyplash qoidalari farq qiladi. Ishonchli shablon tizimiga tayaning va uning avtomatik himoyasini o'chirmang.

XSS himoyasi 2: textContent va innerHTML

Mijoz tomonidagi JavaScript'da eng ko'p uchraydigan XSS sababi โ€” innerHTML ni foydalanuvchi ma'lumoti bilan ishlatish. innerHTML berilgan matnni HTML sifatida talqin qiladi, ya'ni ichidagi teglar ishga tushadi:

// XAVFLI: innerHTML foydalanuvchi ma'lumotini HTML deb talqin qiladi
const izoh = olinganFoydalanuvchiIzohi; // masalan '<img src=x onerror=...>'
element.innerHTML = izoh; // zararli kod ishga tushishi mumkin!

// XAVFSIZ: textContent hamma narsani ODDIY MATN deb qo'yadi
element.textContent = izoh; // teglar matn sifatida ko'rinadi, bajarilmaydi

Qoida oddiy: foydalanuvchi ma'lumotini ko'rsatishda deyarli har doim textContent ishlating, innerHTML emas. Agar HTML tuzilmasi yaratishingiz kerak bo'lsa, elementlarni createElement bilan yarating va matnni textContent orqali qo'ying:

// HTML tuzilmasini xavfsiz yaratish
const div = document.createElement('div');
div.className = 'izoh';

const nom = document.createElement('strong');
nom.textContent = foydalanuvchi.ismi; // xavfsiz

const matn = document.createElement('p');
matn.textContent = foydalanuvchi.izohi; // xavfsiz

div.append(nom, matn);
konteyner.append(div);
Agar foydalanuvchidan cheklangan HTML (masalan, izohlarda qalin matn) qabul qilishingiz shart bo'lsa, uni o'zingiz eskeyplashga urinmang โ€” bu juda xato bo'ladi. DOMPurify kabi sinovdan o'tgan kutubxonadan foydalaning: u xavfli teg va atributlarni olib tashlaydi.

XSS himoyasi 3: Content Security Policy (CSP)

CSP โ€” bu qo'shimcha himoya qatlami. U HTTP sarlavhasi orqali brauzerga aytadi: "faqat shu manbalardagi skriptlarni ishga tushir". Agar XSS'ga yo'l qo'yilgan bo'lsa ham, CSP ruxsat bermagan skript ishga tushmaydi.

// CSP sarlavhasini o'rnatish (Express, helmet orqali)
const helmet = require('helmet');

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],              // faqat o'z domenimizdan
    scriptSrc: ["'self'"],               // inline skript va tashqi skriptlar taqiqlanadi
    styleSrc: ["'self'"],
    imgSrc: ["'self'", 'data:'],
    objectSrc: ["'none'"]                // <object>, <embed> taqiqlanadi
  }
}));

CSP'ning kuchli tomoni: scriptSrc 'self' bo'lsa, HTML ichiga joylashtirilgan (inline) skriptlar ishlamaydi. Bu esa ko'plab XSS hujumlarini avtomatik to'xtatadi, chunki hujumchi joylashtirgan skript aynan inline bo'ladi.

CSP โ€” bu "chuqurlashtirilgan himoya" misoli: u XSS'ni oldini olmaydi, balki agar XSS bo'lsa, uning ta'sirini keskin kamaytiradi. Eskeyplash birinchi mudofaa chizig'i bo'lsa, CSP โ€” ikkinchi. Ikkalasi ham kerak.

CSRF nima?

CSRF (Cross-Site Request Forgery) โ€” bu foydalanuvchining allaqachon login qilgan holatidan foydalanib, uni bilmagan holda biror amalni bajartirishga aldash zaifligi. XSS'dan farqi: bu yerda hujumchi sizning saytingizga kod joylashtirmaydi โ€” u foydalanuvchi brauzerining cookie'ni avtomatik yuborish odatidan foydalanadi.

Muammoning ildizi: brauzer sizning saytingizga so'rov yuborilganda, u saytingiz cookie'larini avtomatik qo'shadi โ€” hattoki so'rov boshqa (zararli) saytdan kelib chiqqan bo'lsa ham. Agar server faqat cookie'ning mavjudligiga ishonsa, u bu so'rovni haqiqiy foydalanuvchining so'rovi deb qabul qiladi.

Masalan, foydalanuvchi bank saytiga login qilgan, cookie'si bor. So'ng u boshqa zararli sahifani ochsa, o'sha sahifa foydalanuvchi bilmagan holda bank saytiga so'rov jo'natishga urinishi mumkin โ€” va brauzer avtomatik ravishda bank cookie'sini biriktiradi.

CSRF asosan holatni o'zgartiruvchi so'rovlarga (pul o'tkazish, parol o'zgartirish, o'chirish) tahdid soladi. Shuning uchun bunday amallar hech qachon oddiy GET so'rov bilan bajarilmasligi kerak โ€” ular POST/PUT/DELETE bo'lishi va CSRF himoyasiga ega bo'lishi lozim.

CSRF himoyasi 1: SameSite cookie

Eng oddiy va samarali zamonaviy himoya โ€” cookie'ga SameSite atributini qo'yish. Bu brauzerga aytadi: bu cookie'ni boshqa saytdan kelgan so'rovlarda yuborma. Shu bilan CSRF hujumining asosiy mexanizmi (cookie'ning avtomatik yuborilishi) buziladi:

// SameSite bilan cookie: boshqa saytdan kelgan so'rovlarda
// cookie yuborilmaydi -> CSRF ning asosiy yo'li yopiladi
res.cookie('sessionId', sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax'  // yoki 'strict' โ€” undan ham qattiqroq
});

SameSite=Lax ko'pchilik holatlar uchun yaxshi standart: u oddiy navigatsiyani buzmaydi, lekin xavfli o'zaro-sayt so'rovlarida cookie'ni yubormaydi. SameSite=Strict yanada qattiqroq, lekin ba'zan foydalanuvchi tajribasiga ta'sir qilishi mumkin.

CSRF himoyasi 2: CSRF token

An'anaviy va ishonchli himoya โ€” CSRF token (yoki "anti-forgery token"). G'oya shunday: server har bir forma uchun tasodifiy, taxmin qilib bo'lmaydigan token yaratadi. Bu token formaga yashirin maydon sifatida qo'yiladi. So'rov kelganda, server tokenning to'g'riligini tekshiradi.

Nima uchun bu ishlaydi? Chunki boshqa (zararli) sayt bu tokenni bila olmaydi โ€” u faqat sizning saytingizdagi haqiqiy formada mavjud. Cookie avtomatik yuborilsa ham, to'g'ri CSRF token yuborilmasa, so'rov rad etiladi.

// Kontseptual CSRF himoya oqimi (Express)
const crypto = require('crypto');

// 1. Forma sahifasini berishda token yaratamiz
app.get('/parol-ozgartirish', (req, res) => {
  const csrfToken = crypto.randomBytes(32).toString('hex');
  req.session.csrfToken = csrfToken; // serverda saqlaymiz
  res.render('parol-forma', { csrfToken });
});

// Formada yashirin maydon:
// <input type="hidden" name="_csrf" value="...token...">

// 2. So'rov kelganda tokenni tekshiruvchi middleware
function csrfTekshir(req, res, next) {
  const yuborilgan = req.body._csrf;
  const saqlangan = req.session.csrfToken;
  if (!yuborilgan || yuborilgan !== saqlangan) {
    return res.status(403).json({ xato: 'CSRF token yaroqsiz' });
  }
  next();
}

app.post('/parol-ozgartirish', csrfTekshir, (req, res) => {
  // Bu yerga faqat to'g'ri CSRF token bilan yetib kelinadi
  // ... parolni o'zgartiramiz
});
Amaliyotda bularni o'zingiz yozish o'rniga sinovdan o'tgan middleware'lardan (masalan, csurf yoki freymvorkning o'z himoyasidan) foydalaning. Yuqoridagi kod tamoyilni ko'rsatish uchun soddalashtirilgan.
Eng yaxshi amaliyot โ€” bir necha qatlamni birlashtirish: SameSite cookie (birinchi mudofaa) + CSRF token (ishonchli ikkinchi qatlam). API'lar uchun, agar autentifikatsiya cookie'da emas, Authorization sarlavhasidagi tokenda bo'lsa, CSRF tabiiy ravishda kamroq muammo bo'ladi (chunki sarlavha avtomatik yuborilmaydi).

XSS va CSRF: farqni eslab qolish

Xulosa