SQL Injection oldini olish
SQL injection — foydalanuvchi kiritgan matn SQL buyrug‘ining bir qismiga aylanib, uning ma’nosini o‘zgartirib yuboradigan hujum. Natijada hujumchi parolsiz tizimga kiradi, boshqalarning ma’lumotini o‘qiydi yoki butun jadvalni o‘chirib yuboradi.
Bu zaiflik 25 yildan ortiq vaqtdan beri ma’lum. Shunga qaramay, u hanuzgacha OWASP Top 10 ro‘yxatidagi eng xavfli zaifliklar qatorida turibdi. Sababi — uni yo‘l qo‘yish juda oson: bitta fmt.Sprintf yetarli.
Hujum qanday ishlaydi?
Login uchun quyidagi kod yozildi deylik:
query := fmt.Sprintf(
"SELECT id FROM users WHERE email = '%s' AND password = '%s'",
email, password,
)
Oddiy foydalanuvchi [email protected] va maxfiy123 kiritadi. Natijada SQL shunday bo‘ladi:
SELECT id FROM users WHERE email = '[email protected]' AND password = 'maxfiy123'
Hammasi to‘g‘ri ishlayapti. Endi hujumchi email maydoniga quyidagi matnni kiritadi:
Satrlar ulanganda:
SELECT id FROM users WHERE email = '[email protected]' --' AND password = '...'
' belgisi satrni yopdi. -- esa SQL’da izoh boshlanishini bildiradi. Parol tekshiruvi izohga aylanib qoldi. Hujumchi Alining akkauntiga parolsiz kirdi.
Yana bir variant. Parol maydoniga ' OR '1'='1 kiritiladi:
'1'='1' doim rost. Shu sababli so‘rov jadvaldagi barcha foydalanuvchilarni qaytaradi va kod birinchisini, odatda administratorni oladi.
Muammoning ildizi: ma’lumot va buyruq bitta satrga aralashib ketdi. Baza qaysi qism dasturchi yozgan buyruq, qaysi qism foydalanuvchi kiritgan ma’lumot ekanini ajrata olmaydi.
Amalda ko‘ramiz
Quyidagi dastur zaif va xavfsiz funksiyani yonma-yon ko‘rsatadi. U oldingi darslarda ishga tushirilgan PostgreSQL’da ishlaydi.
| main.go | |
|---|---|
| |
Zaif funksiya hujumchiga administratorni qaytardi. Xavfsiz funksiya esa [email protected]' OR '1'='1 matnini oddiy satr sifatida qabul qildi va shunday emailli foydalanuvchini qidirdi. Tabiiyki, bunday foydalanuvchi topilmadi.
Asosiy himoya: parametrli so‘rovlar
Parametrli so‘rovda SQL matni va qiymatlar bazaga alohida yuboriladi:
Bazaga yuboriladi:
1. SQL: SELECT name FROM users WHERE email = $1
2. Parametr: [email protected]' OR '1'='1
Baza avval SQL’ni tahlil qiladi va uning tuzilishini aniqlaydi. Parametr keyin, tayyor tuzilmaga qiymat sifatida joylanadi. Qiymat ichida ', -- yoki OR bo‘lsa ham, ular SQL buyrug‘iga aylanmaydi.
PostgreSQL’da parametrlar $1, $2, $3 ko‘rinishida yoziladi. MySQL va SQLite’da esa ? ishlatiladi.
Danger
Qiymatlarni qo‘lda "tozalash" — ' belgisini '' ga almashtirish yoki strings.ReplaceAll bilan xavfli belgilarni olib tashlash — himoya emas. Kodlashlar, maxsus belgilar va baza versiyalari o‘rtasidagi farqlar tufayli bunday tozalashni aylanib o‘tish yo‘llari doim topiladi. Yagona ishonchli usul — parametrlar.
Parametr bilan bo‘lmaydigan joylar
Parametrlar faqat qiymatlar o‘rnida ishlaydi. Jadval nomi, ustun nomi, ASC/DESC yo‘nalishi kabi SQL identifikatorlari va kalit so‘zlarini parametr qilib bo‘lmaydi:
// ❌ Ishlamaydi: $1 ustun nomi emas, satr qiymati sifatida qabul qilinadi
db.Query(ctx, "SELECT * FROM books ORDER BY $1", sortColumn)
Aynan shu joylarda dasturchilar ko‘pincha fmt.Sprintfga qaytadi va zaiflik paydo bo‘ladi.
Yechim: ruxsat ro‘yxati (allowlist)
Klient ustun nomini emas, kalitni yuboradi. Server esa kalitni oldindan tayyorlab qo‘yilgan SQL bo‘lagiga moslaydi:
var sortColumns = map[string]string{
"title": "title",
"year": "year",
"newest": "created_at",
}
func orderBy(sort, dir string) (string, error) {
col, ok := sortColumns[sort]
if !ok {
return "", fmt.Errorf("saralash maydoni noto‘g‘ri: %q", sort)
}
direction := "ASC"
if dir == "desc" {
direction = "DESC"
}
return " ORDER BY " + col + " " + direction, nil
}
SQL’ga qo‘shilayotgan har bir so‘z dasturchi yozgan konstantalardan olinadi. Klient kiritgan matn so‘rovga hech qachon to‘g‘ridan-to‘g‘ri tushmaydi. Ro‘yxatda bo‘lmagan qiymat esa xato bilan rad etiladi.
LIKE bilan qidirish
Qidiruv so‘rovlarida % shabloni qiymatga qo‘shiladi, SQL matniga emas:
// ❌ Zaif
query := "SELECT title FROM books WHERE title ILIKE '%" + q + "%'"
// ✅ Xavfsiz
rows, err := db.Query(ctx, "SELECT title FROM books WHERE title ILIKE $1", "%"+q+"%")
Bu SQL injection’dan himoya qiladi. Lekin yana bir nozik jihat bor: % va _ belgilari LIKE ichida maxsus ma’noga ega. Foydalanuvchi % qidirsa, u barcha qatorlarga mos keladi. Bu xavfsizlik teshigi emas, lekin kutilmagan natija beradi va og‘ir so‘rovga olib kelishi mumkin. Ularni ekranlab qo‘yish yaxshi:
func escapeLike(s string) string {
r := strings.NewReplacer(`\`, `\\`, `%`, `\%`, `_`, `\_`)
return r.Replace(s)
}
db.Query(ctx, `SELECT title FROM books WHERE title ILIKE $1`, "%"+escapeLike(q)+"%")
PostgreSQL’da \ — LIKE uchun standart ekranlash belgisi.
IN ro‘yxati
Bir nechta ID bo‘yicha qidirishda ko‘pincha satrlarni vergul bilan ulash vasvasasi paydo bo‘ladi:
PostgreSQL’da massivni bitta parametr sifatida yuborish mumkin. pgx Go slice’ini PostgreSQL massiviga o‘zi aylantiradi:
// ✅ Xavfsiz
ids := []int64{1, 3, 7}
rows, err := db.Query(ctx, "SELECT id, title FROM books WHERE id = ANY($1)", ids)
Dinamik filtrlar
Qidiruv formasida ixtiyoriy filtrlar ko‘p bo‘ladi: muallif, yil oralig‘i, janr. Shartlarni dinamik qo‘shish kerak, lekin qiymatlar baribir parametr bo‘lib qolishi lozim:
SELECT id, title, author, year FROM books WHERE author = $1 AND year >= $2 AND title ILIKE $3 ORDER BY id
[]interface {}{"Abdulla Qodiriy", 1920, "%kun'; DROP TABLE books; --%"}
fmt.Sprintf bu yerda ham ishlatildi, lekin faqat parametr raqamini ($1, $2) qo‘yish uchun. Bu raqamni kod o‘zi hisoblaydi. Foydalanuvchi kiritgan matnlar esa faqat args ro‘yxatiga tushadi. Hujum qilishga urinilgan DROP TABLE qatori oddiy qidiruv matni bo‘lib qoldi.
'ހ' — bu 1920 soni. %q formati intni belgi sifatida ko‘rsatadi. Bu yerda muhimi shundaki, qiymat SQL matnida emas, parametrlar ro‘yxatida turibdi.
Qo‘shimcha himoya qatlamlari
Parametrli so‘rovlar asosiy himoya. Lekin xavfsizlik bir necha qatlamdan iborat bo‘lishi kerak:
- Eng kam huquq tamoyili. Ilova bazaga superuser sifatida ulanmasin. Unga faqat kerakli jadvallarda
SELECT,INSERT,UPDATE,DELETEhuquqlarini bering.DROP TABLEyoki boshqa bazalarga kirish huquqi bo‘lmasin. Shunda zaiflik topilgan taqdirda ham zarar cheklangan bo‘ladi. - Xato xabarlarini yashirish. Baza xatosining matnini klientga yubormang. Unda jadval va ustun nomlari bo‘ladi. Xatoliklarni standart formatda qaytarish darsidagi yondashuv shu muammoni hal qiladi.
- Kirish validatsiyasi. Email email formatida, ID esa son bo‘lishi kerak. Validatsiya injection’ni to‘liq to‘xtatmaydi, lekin hujum imkoniyatlarini kamaytiradi.
- Kod tekshiruvi.
goseckabi statik tahlil vositalari SQL satrini birlashtirish holatlarini topib beradi:
Xulosa
- SQL injection ma’lumot va buyruq bitta satrga aralashganda yuz beradi.
- Qiymatlarni doim parametr (
$1,$2) orqali uzating. SQL matninifmt.Sprintfyoki+bilan foydalanuvchi ma’lumotidan yig‘mang. - Qo‘lda tozalash himoya emas.
- Ustun nomlari va saralash yo‘nalishi parametr bo‘la olmaydi. Ular faqat ruxsat ro‘yxati orqali tanlanadi.
LIKEuchun%qiymatga qo‘shiladi,INo‘rniga= ANY($1)ishlatiladi.- Bazaga ilova uchun kerakli minimal huquqlar bilan ulaning.
Keyingi darsda yana bir muhim mavzu — parollarni bazada xavfsiz saqlashni ko‘ramiz.