Web xavfsizligi va autentifikatsiya

SQL injection va boshqa himoyalar

Injection (kiritish) hujumlari OWASP ro'yxatida yillar davomida yuqori o'rinda turadi. Ular orasida eng mashhuri โ€” SQL injection. Ushbu yakuniy darsda biz SQL injection qanday paydo bo'lishini va undan qanday himoyalanishni ko'rib chiqamiz, so'ng backend xavfsizligini mustahkamlaydigan bir qator muhim amaliyotlarni jamlaymiz.

SQL injection qanday yuzaga keladi?

SQL injection zaifligi foydalanuvchi kiritmasi SQL so'rovi matniga to'g'ridan-to'g'ri qo'shilganda paydo bo'ladi. Muammoning ildizi shu: agar kiritma so'rov matniga aralashsa, brauzer/ma'lumotlar bazasi qayerda ma'lumot tugab, qayerda buyruq boshlanishini farqlay olmaydi.

Xavfli naqsh โ€” bu so'rovni satrlarni ulash (string concatenation) orqali qurish:

// XAVFLI: foydalanuvchi kiritmasi so'rov MATNIGA ulanmoqda
const email = req.body.email; // foydalanuvchi nazoratidagi qiymat
const sorov = "SELECT * FROM users WHERE email = '" + email + "'";
db.query(sorov);

// Agar 'email' oddiy matn bo'lmasa, balki SQL sintaksisini
// o'z ichiga olsa, so'rovning ma'nosi butunlay o'zgarib ketishi mumkin.
// Server buni ODDIY MATN emas, BUYRUQ deb talqin qiladi.

Xuddi XSS singari, bu yerdagi asosiy muammo bir xil: ma'lumot bilan kod aralashib ketmoqda. Himoyaning kaliti โ€” ma'lumotni doim ma'lumot sifatida, koddan qat'iy ajratib ishlatish.

Foydalanuvchi kiritmasini hech qachon SQL so'rovi matniga (yoki har qanday buyruq matniga: shell buyrug'i, NoSQL so'rovi, LDAP filtri) satr ulash orqali qo'shmang. Bu injection zaifligining asosiy sababidir.

Himoya 1: Parametrlangan so'rovlar (prepared statements)

Eng ishonchli himoya โ€” parametrlangan so'rovlar (prepared statements). Bunda siz so'rov tuzilmasini alohida, qiymatlarni alohida beraytasiz. Ma'lumotlar bazasi qiymatlarni doim faqat ma'lumot deb qabul qiladi, hech qachon SQL buyruq deb emas โ€” ular so'rov matniga aralashmaydi.

// XAVFSIZ: parametrlangan so'rov (?, $1 kabi placeholder'lar)
// mysql2 kutubxonasi bilan misol:
const email = req.body.email;

const [natijalar] = await db.execute(
  'SELECT * FROM users WHERE email = ?', // so'rov tuzilmasi
  [email]                               // qiymatlar ALOHIDA beriladi
);

// 'email' ichida nima bo'lishidan qat'i nazar, u faqat
// email QIYMATI sifatida ishlatiladi, buyruq sifatida emas.
// PostgreSQL (node-postgres / pg) bilan: $1, $2 placeholder'lar
const natija = await pool.query(
  'SELECT * FROM users WHERE email = $1 AND rol = $2',
  [email, rol]
);
Placeholder (? yoki $1) โ€” bu shunchaki matn almashtirish EMAS. Ma'lumotlar bazasi so'rov rejasini avval qiymatsiz tayyorlaydi, keyin qiymatlarni xavfsiz bog'laydi. Aynan shu ajratish injection'ni imkonsiz qiladi.

Himoya 2: ORM va so'rov quruvchilar

Zamonaviy loyihalarda ko'pincha ORM (Object-Relational Mapping) yoki so'rov quruvchi (query builder) ishlatiladi โ€” masalan, Prisma, Sequelize, Knex, TypeORM. Ular so'rovlarni ichki tarzda parametrlashtiradi, shu sabab injection'dan tabiiy himoya beradi:

// Prisma bilan: qiymatlar avtomatik xavfsiz parametrlanadi
const foydalanuvchi = await prisma.user.findUnique({
  where: { email: req.body.email }
});

// Sequelize bilan:
const foydalanuvchi2 = await User.findOne({
  where: { email: req.body.email }
});

// Ikkalasida ham 'email' xom SQL matniga aralashmaydi.
ORM ham panatseya emas. Agar ORM ichida xom SQL yozish imkoniyatidan foydalansangiz (masalan, sequelize.query yoki raw so'rov), o'sha yerda satr ulashdan qoching โ€” parametrlangan variantini ishlating. ORM sizni faqat to'g'ri ishlatilgandagina himoya qiladi.

Boshqa himoya 1: Kiritishni validatsiya qilish

Injection'dan tashqari ham, har bir kiruvchi ma'lumotni server tomonida tekshiring: kutilgan turdami, formatdami, chegaradami? Bu "foydalanuvchiga ishonma" tamoyilining amaliy ko'rinishi. Validatsiya kutilmagan ma'lumotni erta rad etadi.

// Validatsiya kutubxonasi (masalan, zod) bilan misol
const { z } = require('zod');

const royxatSxemasi = z.object({
  email: z.string().email(),
  parol: z.string().min(8).max(128),
  yosh: z.number().int().min(0).max(150)
});

app.post('/royxat', (req, res) => {
  const natija = royxatSxemasi.safeParse(req.body);
  if (!natija.success) {
    return res.status(400).json({ xato: 'Kiritma noto'g'ri' });
  }
  const malumot = natija.data; // endi bu ma'lumot tekshirilgan va toza
  // ...
});
Validatsiyada ruxsat berilganlar ro'yxati (allowlist) yondashuvini afzal ko'ring: "faqat shu qiymatlarga ruxsat" degan qoida "bu yomon qiymatlarni taqiqlayman" degandan ishonchliroq, chunki barcha yomon qiymatlarni oldindan bilib bo'lmaydi.

Boshqa himoya 2: Eng kam imtiyoz tamoyili

Eng kam imtiyoz (least privilege) โ€” har bir komponent faqat o'z ishini bajarishga yetadigan huquqqa ega bo'lsin, ortiqchasiga emas. Bu zaiflik yuz berganda ham zararni cheklaydi:

Eng kam imtiyoz โ€” bu chuqurlashtirilgan himoyaning bir qismi. Agar bitta qatlam buzilsa ham (masalan, injection yuz bersa), cheklangan huquqlar tufayli zarar kichik bo'ladi.

Boshqa himoya 3: Sirlarni .env'da saqlash

Maxfiy kalitlar (JWT siri, ma'lumotlar bazasi paroli, API kalitlari) hech qachon kodga yozilmasligi va git tarixiga tushmasligi kerak. Ularni muhit o'zgaruvchilarida saqlang va .env faylini .gitignore'ga qo'shing:

// .env fayli (BU FAYL git'ga QO'SHILMAYDI)
// DATABASE_URL=postgres://user:parol@host:5432/baza
// JWT_SECRET=juda-uzun-tasodifiy-maxfiy-qiymat
// API_KEY=...

// Kodda sirlarni process.env orqali o'qiymiz
require('dotenv').config();

const JWT_SIR = process.env.JWT_SECRET;
if (!JWT_SIR) {
  throw new Error('JWT_SECRET o'rnatilmagan!'); // ishga tushishda tekshiramiz
}
# .gitignore faylida albatta bo'lsin:
.env
.env.local
node_modules/
Agar sir bir marta git tarixiga tushib qolgan bo'lsa, uni .gitignore'ga qo'shish yetarli emas โ€” u tarixda qoladi. Bunday holatda sirni darhol yangilang (rotate qiling), chunki uni fosh bo'lgan deb hisoblash kerak.

Boshqa himoya 4: Rate limiting

Rate limiting (so'rov chastotasini cheklash) โ€” bir manbadan kelgan so'rovlar sonini vaqt oralig'ida cheklaydi. Bu parolni takror-takror urinib topishga urinishlar va xizmatni yuklama bilan bostirishga qarshi himoya (mavjudlik va autentifikatsiya himoyasi):

const rateLimit = require('express-rate-limit');

// Login uchun qattiqroq cheklov
const loginCheklovi = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 daqiqa
  max: 5,                   // har IP dan 15 daqiqada 5 urinish
  message: { xato: 'Juda ko'p urinish. Birozdan keyin qayta urining.' },
  standardHeaders: true,
  legacyHeaders: false
});

app.post('/login', loginCheklovi, loginHandler);

// Umumiy API uchun yumshoqroq cheklov
const umumiyCheklov = rateLimit({ windowMs: 60 * 1000, max: 100 });
app.use('/api/', umumiyCheklov);
Login, parol tiklash va ro'yxatdan o'tish kabi maxsus endpointlarga qattiqroq cheklov qo'ying. Bu avtomatlashtirilgan parol topish urinishlarini samarasiz qiladi.

Boshqa himoya 5: Xatoliklarni yashirish

Batafsil xato xabarlari โ€” ayniqsa stack trace, SQL xatosi matni, fayl yo'llari โ€” foydalanuvchiga ko'rsatilsa, ular tizim tuzilishi haqida keraksiz ma'lumot beradi. Ishlab chiqarish (production) muhitida foydalanuvchiga umumiy xabar bering, batafsil xatoni esa faqat serverning ichki loglariga yozing:

// Markazlashtirilgan xato ishlovchi (Express)
app.use((err, req, res, next) => {
  // Batafsil xatoni faqat SERVER loglariga yozamiz
  console.error(err.stack);

  // Foydalanuvchiga UMUMIY xabar (ichki tafsilotlarni yashiramiz)
  res.status(500).json({ xato: 'Serverda xatolik yuz berdi' });
});
Production'da NODE_ENV=production o'rnating. Ba'zi freymvorklar development rejimida batafsil xato sahifalarini ko'rsatadi โ€” bu sahifalar tizim ichki tuzilishini fosh qilishi mumkin. Foydalanuvchiga hech qachon xom stack trace ko'rsatmang.

Yakuniy nazorat ro'yxati

Ushbu bobni yakunlab, backend xavfsizligining amaliy nazorat ro'yxatini jamlaymiz:

Xulosa