SQL Injection — qanday ishlaydi, qanday himoyalanish

~20 daq · MySQL · Xavfsizlik va Boshqaruv

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, "..." % qiymat formatlash 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, UNION bilan 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