SQL Injection — qanday ishlaydi, qanday himoyalanish
Muammodan boshlaylik
Mahsulot qidiruvi. Ilova kodi (tili muhim emas):
sql = "SELECT id, nom, narx FROM mahsulotlar WHERE nom LIKE '%" + qidiruv + "%'"
Oddiy foydalanuvchi telefon yozadi — hammasi joyida:
SELECT id, nom, narx FROM mahsulotlar WHERE nom LIKE '%telefon%'
Hujumchi esa shuni yozadi:
' UNION SELECT id, email, parol_hash FROM foydalanuvchilar --
Natijada SQL:
SELECT id, nom, narx FROM mahsulotlar WHERE nom LIKE '%'
UNION SELECT id, email, parol_hash FROM foydalanuvchilar -- %'
Qidiruv natijalari sahifasida — barcha foydalanuvchilarning emaillari va parol xeshlari.
Bu — SQL injection. Bir necha o'n yillardan beri veb-ilovalardagi eng xavfli zaifliklar ro'yxatining yuqorisida turadi. Millionlab foydalanuvchi ma'lumotlarining o'g'irlanishiga aynan shu bitta xato sabab bo'lgan.
Qanday ishlaydi
Muammoning ildizi — kod va ma'lumotni aralashtirish. SQL so'rov — kod. Foydalanuvchi kiritgan narsa — ma'lumot. Ularni string sifatida birlashtirsak — MySQL qayerda kod tugab, ma'lumot boshlanganini bilmaydi.
Hujumchi ' belgisi bilan bizning matnimizni yopadi va o'z SQL kodini yozadi. -- (izoh) bilan qolgan qismni "o'chiradi".
Hujum turlari
1. Autentifikatsiyani aylanib o'tish
sql = "SELECT * FROM foydalanuvchilar WHERE email = '" + email + "' AND parol = '" + parol + "'"
Email maydoniga: admin@dokon.uz' --
SELECT * FROM foydalanuvchilar WHERE email = 'admin@dokon.uz' -- ' AND parol = '...'
Parol tekshiruvi izohga aylandi. Admin sifatida kirildi.
2. UNION — boshqa jadvallarni o'qish
Yuqoridagi qidiruv misoli. Hujumchi avval ustunlar sonini topadi (ORDER BY 1, ORDER BY 2... xato chiqquncha), keyin UNION SELECT bilan istalgan jadvalni o'qiydi. information_schema.TABLES dan — barcha jadvallar nomlarini.
3. Raqamli parametrlar — qo'shtirnoqsiz
sql = "SELECT * FROM buyurtmalar WHERE id = " + id
id = 1 OR 1=1 → barcha buyurtmalar. id = 1; DROP TABLE buyurtmalar → ba'zi drayverlarda ko'p so'rov ham ishlaydi.
"Raqam — xavfsiz" degan fikr — xato.
4. Blind injection — "ko'r" hujum
Natija sahifada ko'rinmasa ham:
id = 1 AND (SELECT SUBSTRING(parol_hash, 1, 1) FROM foydalanuvchilar WHERE id = 1) = '#39;
Sahifa normal ochilsa — birinchi belgi $. Bo'lmasa — boshqa belgi. Harfma-harf, avtomatlashtirilgan vositalar bilan — butun bazani o'qish mumkin. Yoki SLEEP(5) bilan — javob kechiksa, shart to'g'ri.
5. Ikkinchi darajali (second-order)
Hujumchi ro'yxatdan o'tishda ism sifatida Ali' -- yozadi. Bu xavfsiz saqlanadi. Lekin keyin boshqa joyda — admin panelda — ism bazadan olinib, xavfsiz bo'lmagan so'rovga qo'shiladi. "Bazadan kelgan ma'lumot — ishonchli" degan fikr ham xato.
Himoya: prepared statements
Yagona ishonchli himoya — so'rov shablonini va ma'lumotni alohida yuborish:
-- Shablon (kod)
SELECT id, nom, narx FROM mahsulotlar WHERE nom LIKE ?
-- Ma'lumot alohida
['%telefon%']
MySQL avval shablonni tahlil qiladi va tuzilishini qotiradi. Keyin kelgan qiymat — faqat ma'lumot. ' UNION SELECT ... yozilsa — MySQL aynan shu matnni nomda qidiradi. Hech narsa topmaydi. Hech qanday kod bajarilmaydi.
Turli tillarda
<?php
// PHP — PDO
$stmt = $pdo->prepare('SELECT id, nom, narx FROM mahsulotlar WHERE nom LIKE ?');
$stmt->execute(['%' . $qidiruv . '%']);
// Node.js — mysql2
const [rows] = await pool.execute(
'SELECT id, nom, narx FROM mahsulotlar WHERE nom LIKE ?',
[`%${qidiruv}%`]
);
# Python — mysql-connector
cursor.execute("SELECT id, nom, narx FROM mahsulotlar WHERE nom LIKE %s", (f"%{qidiruv}%",))
-- Stored procedure ichida dinamik SQL
SET @q = CONCAT('%', p_qidiruv, '%');
PREPARE st FROM 'SELECT id, nom FROM mahsulotlar WHERE nom LIKE ?';
EXECUTE st USING @q;
DEALLOCATE PREPARE st;
[!WARNING] Python dagi
%s— placeholder,"..." % qiymatformatlash emas.cursor.execute("... LIKE '%s'" % qidiruv)— bu yana string birlashtirish va zaif. Qiymat doim ikkinchi argument sifatida.
Placeholder ishlamaydigan joylar
Placeholder faqat qiymat o'rnida ishlaydi. Jadval nomi, ustun nomi, ASC/DESC, LIMIT (ba'zi drayverlarda) — placeholder bilan berib bo'lmaydi.
❌ "SELECT * FROM mahsulotlar ORDER BY " + saralash
Yechim — whitelist (ruxsat etilganlar ro'yxati):
<?php
$ruxsat = ['nom' => 'nom', 'narx' => 'narx', 'yangi' => 'created_at'];
$ustun = $ruxsat[$_GET['saralash'] ?? ''] ?? 'nom';
$yonalish = ($_GET['tartib'] ?? '') === 'desc' ? 'DESC' : 'ASC';
$sql = "SELECT id, nom, narx FROM mahsulotlar ORDER BY $ustun $yonalish LIMIT ?";
SQL ga faqat sizning ro'yxatingizdagi qiymatlar tushadi. Foydalanuvchi faqat ulardan birini tanlaydi.
IN (...) ro'yxati — ? lar soni dinamik yasaladi, qiymatlar baribir placeholder orqali (PHP kursi, 36-dars).
Yetarli EMAS usullar
| Usul | Nima uchun yetarli emas |
|---|---|
addslashes(), qo'lda ' ni \' ga almashtirish |
Kodlash hiylalari, raqamli maydonlar, izohlar orqali aylanib o'tiladi |
Qora ro'yxat (UNION, DROP so'zlarini taqiqlash) |
UnIoN, UN/**/ION, kodlangan variantlar — cheksiz |
| Frontend da tekshirish | Hujumchi brauzerni chetlab, to'g'ridan-to'g'ri so'rov yuboradi |
mysqli_real_escape_string |
To'g'ri ishlatilsa ishlaydi, lekin bitta joyda unutish — teshik; raqamli maydonlarda foydasiz |
| "Bu maydon admin uchun, xavfsiz" | Admin akkaunti ham buzilishi mumkin; second-order hujumlar |
Qo'shimcha himoya qatlamlari
Prepared statements — asosiy himoya. Qolganlari — xato bo'lib qolgan joyda zararni kamaytirish uchun:
1. Eng kam huquq (50-dars). Ilova foydalanuvchisida DROP, CREATE USER, FILE huquqlari yo'q. Boshqa bazalarga kirish yo'q.
2. Xato xabarlarini yashirish. You have an error in your SQL syntax near 'UNION...' — hujumchiga qimmatli maslahat. Production da foydalanuvchiga umumiy xabar, tafsilot — logga.
3. ORM / query builder. Laravel Eloquent, Prisma, Sequelize — standart holatda prepared statements ishlatadi. Lekin "xom SQL" funksiyalari (DB::raw(), $queryRawUnsafe, sequelize.query()) ichiga o'zgaruvchi qo'shsangiz — himoya yo'qoladi.
4. Kiritishni tekshirish. id — butun son bo'lishi kerakmi? filter_var(..., FILTER_VALIDATE_INT). Bu injection himoyasi emas, lekin hujum yuzasini qisqartiradi.
5. WAF (Web Application Firewall) — Cloudflare, ModSecurity. Tipik hujum naqshlarini to'xtatadi. Oxirgi qatlam, asosiy himoya emas.
O'zingizni tekshiring
Kod reviewda qidiring:
🔍 so'rov ichida + , . , ${ , f" , % , .format( , CONCAT( bilan o'zgaruvchi
🔍 query( , raw( , exec( , Unsafe
Har bir topilgan joy — o'zgaruvchi foydalanuvchidan kelishi mumkinmi? Bo'lsa — prepared statement ga o'tkazing.
Avtomatik vositalar: sqlmap (o'z saytingizni sinash uchun, faqat ruxsat bilan), statik tahlil (SonarQube, Semgrep).
Nima uchun shunday?
Nima uchun 25 yildan beri ma'lum bo'lgan va oddiy yechimga ega zaiflik hali ham uchraydi?
Chunki string birlashtirish — eng tabiiy yo'l: har bir dasturchi "... WHERE id = " + id ni birinchi kunidan yozadi va u ishlaydi. Xato faqat hujum paytida ko'rinadi. Eski darsliklar va "tez yechimlar" hali ham shunday kod ko'rsatadi. Va katta loyihada 1000 ta so'rovdan bittasida unutish yetarli. Shuning uchun qoida mutlaq bo'lishi kerak: SQL ichiga foydalanuvchi ma'lumoti hech qachon string sifatida qo'shilmaydi — istisnosiz.
Xulosa
Eslab qoling:
- SQL injection — foydalanuvchi ma'lumoti SQL kodi sifatida bajariladi
- Hujumlar: login aylanib o'tish,
UNIONbilan o'g'irlash, blind, second-order - Raqamli parametrlar ham, bazadan kelgan ma'lumot ham xavfli
- Yagona ishonchli himoya — prepared statements (placeholder
?) - Ustun nomlari,
ASC/DESC— faqat whitelist addslashes, qora ro'yxatlar, frontend tekshiruv — yetarli emas- Qo'shimcha qatlamlar: eng kam huquq, xatolarni yashirish, ORM (raw SQL dan ehtiyot)
Keyingi dars
52-dars: Backup va Restore — mysqldump, mysqlpump