Input sanitizatsiyasi
API’ga keladigan har qanday ma’lumot — JSON tana, URL, sarlavha, cookie yoki yuklangan fayl — ishonchsiz hisoblanadi. U mobil ilovangizdan kelgan bo‘lsa ham, chunki so‘rovni curl bilan ham yasash mumkin. Bu qoidaning qisqacha ifodasi: "Foydalanuvchi kiritgan ma’lumotga hech qachon ishonmang".
Sanitizatsiya (sanitization) — kiruvchi ma’lumotni tozalash va xavfsiz ko‘rinishga keltirish. Lekin bu so‘z ko‘pincha noto‘g‘ri tushuniladi: "xavfli belgilarni olib tashlasak, hammasi joyida bo‘ladi". Amalda esa himoya uch xil vositadan iborat va har biri o‘z joyida ishlatiladi.
Uch xil vosita
| Vosita | Nima qiladi | Qachon |
|---|---|---|
| Validatsiya | Qoidaga mos kelmagan ma’lumotni rad etadi | Kirishda, har doim |
| Normallashtirish | Ma’lumotni yagona ko‘rinishga keltiradi | Kirishda, saqlashdan oldin |
| Ekranlash (encoding) | Ma’lumotni chiqish joyiga mos kodlaydi | Chiqishda: SQL, HTML, shell, log |
Muhim tamoyil: kirishda tekshiring, chiqishda ekranlang.
Nima uchun "xavfli belgilarni olib tashlash" yetarli emas? Qaysi belgi xavfli ekani ma’lumot qayerga borishiga bog‘liq:
- SQL uchun
'xavfli; - HTML uchun
<va>xavfli; - shell uchun
;,|va$xavfli; - fayl yo‘li uchun
..va/xavfli.
Kirish paytida ma’lumot keyin qayerda ishlatilishini bilmaysiz. O'Brien familiyasidagi ' belgisini olib tashlasangiz, ism buziladi. Uni saqlab, SQL’da parametr sifatida, HTML’da esa html/template orqali ishlatsangiz, ism ham to‘g‘ri qoladi, xavf ham bo‘lmaydi.
1. Validatsiya: ruxsat ro‘yxati
Validatsiyaning ikki yondashuvi bor:
- denylist (taqiqlar ro‘yxati) — "
<script>bo‘lmasin". Hujumchi ro‘yxatda yo‘q variantni topadi:<ScRiPt>,<img onerror=...>va hokazo; - allowlist (ruxsat ro‘yxati) — "faqat harf, raqam va
_". Ro‘yxatda yo‘q hamma narsa rad etiladi.
Iloji bo‘lgan joyda doim allowlist tanlang:
var usernameRe = regexp.MustCompile(`^[a-z0-9_]{3,20}$`)
if !usernameRe.MatchString(username) {
return errors.New("login 3–20 ta kichik lotin harfi, raqam yoki _ belgisidan iborat bo‘lishi kerak")
}
Validatsiya qoidalarini go-playground/validator bilan yozish qulay: oneof, alphanum, email, uuid4, min, max.
2. Normallashtirish
Bir xil ma’noli ma’lumot turli ko‘rinishda kelishi mumkin. Normallashtirish uni bitta ko‘rinishga keltiradi.
func cleanText(s string, maxRunes int) (string, error) {
if !utf8.ValidString(s) {
return "", errors.New("matn UTF-8 formatida emas")
}
s = strings.Map(func(r rune) rune {
switch {
case r == '\n' || r == '\t':
return r // ko‘p qatorli matn uchun ruxsat
case unicode.IsControl(r):
return -1 // boshqaruv belgilari olib tashlanadi
case unicode.Is(unicode.Cf, r):
return -1 // ko‘rinmas formatlash belgilari: , ...
}
return r
}, s)
s = strings.TrimSpace(s)
if utf8.RuneCountInString(s) > maxRunes {
return "", fmt.Errorf("matn %d belgidan oshmasligi kerak", maxRunes)
}
return s, nil
}
Nimalar tozalanadi va nima uchun:
- Noto‘g‘ri UTF-8. Buzilgan baytlar bazaga yozishda xato beradi yoki boshqa tizimlarda kutilmagan natija chiqaradi.
- Boshqaruv belgilari (
\x00,\x1bva boshqalar).\x00ba’zi tizimlarda satrni kesib qo‘yadi.\x1bterminal buyruqlarini boshlaydi, shuning uchun loglarni terminalda ko‘rganda kutilmagan natija berishi mumkin. - Ko‘rinmas belgilar.
(nol kenglikdagi bo‘shliq)adminvaadminsatrlarini ko‘zga bir xil ko‘rsatadi.esa matn yo‘nalishini teskari qiladi:rekcah.exefayl nomiexe.hackerbo‘lib ko‘rinishi mumkin. - Uzunlik belgilarda o‘lchanadi.
len()baytlarni sanaydi."O‘zbekiston"satri 11 belgidan iborat, lekin 13 bayt.
Email va login uchun qo‘shimcha ravishda harflar kichik qilinadi: strings.ToLower. Bu Login / Signup darsida ko‘rilgan.
Info
Unicode’da bitta harf turli usullarda yozilishi mumkin: é bitta belgi sifatida ham, e va urg‘u belgisi jufti sifatida ham. Ular ko‘zga bir xil ko‘rinadi, lekin baytlari farq qiladi. Bunday matnlarni solishtirish yoki noyoblikni tekshirish muhim bo‘lsa, golang.org/x/text/unicode/norm paketi bilan NFC shakliga keltiring: norm.NFC.String(s).
3. Fayl yo‘llari: path traversal
Foydalanuvchi bergan nom bilan fayl ochish — eng xavfli joylardan biri:
name qiymati ../../etc/passwd bo‘lsa, filepath.Join natijasi ../etc/passwd bo‘ladi. Server uploads katalogidan chiqib, tizim fayliga yetib boradi. Bu hujum path traversal deb ataladi.
Go 1.24 versiyasida bu muammo uchun maxsus vosita qo‘shildi: os.Root. U fayl tizimiga murojaatni bitta katalog bilan cheklaydi. Na .., na absolyut yo‘l, na katalogdan tashqariga ishora qiluvchi symlink undan chiqib keta oladi:
root, err := os.OpenRoot("uploads")
if err != nil {
log.Fatal(err)
}
defer root.Close()
f, err := root.Open(name) // ../../etc/passwd → xato
os.Rootdan tashqari yana ikki qoida bor:
- fayllarni foydalanuvchi bergan nom bilan saqlamang. Diskda tasodifiy ID bilan saqlang, asl nomni esa bazada alohida yozib qo‘ying;
- statik fayllarni tarqatish uchun
http.FileServeryokihttp.FileServerFSishlating. Ular..segmentlarini o‘zi tozalaydi.
4. Tashqi buyruqlar
Ba’zan server tashqi dasturni ishga tushiradi: rasmni kichraytirish, PDF yaratish va hokazo.
// ❌ XAVFLI: satr shell orqali bajariladi
exec.Command("sh", "-c", "convert "+filename+" -resize 200x200 out.png")
// ✅ XAVFSIZ: argumentlar alohida uzatiladi, shell yo‘q
exec.Command("convert", "--", filename, "-resize", "200x200", "out.png")
Birinchi variantda filename qiymati x.png; rm -rf / bo‘lsa, shell ikkita buyruqni bajaradi. Ikkinchisida esa exec.Command dasturni to‘g‘ridan-to‘g‘ri ishga tushiradi va har bir argumentni o‘zgartirmasdan uzatadi. ; belgisi oddiy belgi bo‘lib qoladi.
-- belgisi "bundan keyin faqat fayl nomlari keladi" degan ma’noni bildiradi. U -rf kabi - bilan boshlanadigan nom dasturning parametri deb tushunilishining oldini oladi.
5. Log injection
Foydalanuvchi ma’lumoti logga yozilganda ham ehtiyot bo‘lish kerak:
username qiymati ali\n2026/09/30 10:00:00 login muvaffaqiyatli: admin bo‘lsa, logda soxta qator paydo bo‘ladi. Loglarni tahlil qiladigan odam yoki dastur chalg‘iydi.
Yechim — tuzilgan loglar. slog.JSONHandler qiymatlarni JSON satri sifatida yozadi va \n belgisini ekranlaydi:
slog.Info("login urinishi", "username", username)
// {"msg":"login urinishi","username":"ali\n2026/09/30 ... admin"}
Soxta qator paydo bo‘lmadi, u bitta maydon ichida qoldi.
6. Fayl yuklash
Fayl yuklash endpointi uchun qoidalar ro‘yxati:
func upload(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 5<<20) // 5 MB
file, _, err := r.FormFile("avatar")
if err != nil {
http.Error(w, "fayl topilmadi yoki juda katta", http.StatusBadRequest)
return
}
defer file.Close()
head := make([]byte, 512)
n, _ := io.ReadFull(file, head)
contentType := http.DetectContentType(head[:n])
allowed := map[string]string{"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}
ext, ok := allowed[contentType]
if !ok {
http.Error(w, "faqat PNG, JPEG yoki WebP", http.StatusUnsupportedMediaType)
return
}
name := randomName() + ext // crypto/rand asosidagi nom; foydalanuvchi bergan nom ishlatilmaydi
// ... faylni root.Create(name) bilan saqlash
}
- Hajm cheklanadi, ham butun so‘rov, ham fayl uchun.
- Turi tarkibiga qarab aniqlanadi. Fayl kengaytmasiga ham, klient yuborgan
Content-Typesarlavhasiga ham ishonib bo‘lmaydi:virus.exefaylinicat.pngdeb nomlash oson.http.DetectContentTypefaylning birinchi 512 baytiga qaraydi. - Nom server tomonidan yaratiladi.
- Fayllar veb-server kodi bilan bir katalogda emas, alohida joyda saqlanadi. Ular tarqatilganda
Content-Disposition: attachmentvaX-Content-Type-Options: nosniffsarlavhalari qo‘yiladi.
7. Boy matn (HTML) qabul qilish
Ba’zan foydalanuvchiga formatlangan matn kiritishga ruxsat berish kerak bo‘ladi: blog maqolasi yoki izohdagi qalin matn va havolalar. Bu holatda HTML’ni ekranlab bo‘lmaydi, aks holda formatlash yo‘qoladi. HTML’ni qo‘lda regex bilan tozalash esa deyarli har doim chetlab o‘tiladi.
Buning uchun maxsus kutubxonalar ishlatiladi. Go’da keng tarqalgani — github.com/microcosm-cc/bluemonday. U ruxsat berilgan teglar va atributlardan boshqa hammasini olib tashlaydi:
p := bluemonday.UGCPolicy() // foydalanuvchi kontenti uchun tayyor siyosat
safe := p.Sanitize(`<b>Salom</b><script>alert(1)</script><a href="javascript:x">link</a>`)
// <b>Salom</b>link
Undan ham soddaroq yo‘l — HTML o‘rniga Markdown qabul qilish va uni serverda xavfsiz HTML’ga aylantirish.
To‘liq misol
" Salom, dunyo! " → "Salom, dunyo!" err=<nil>
"admin" → "admin" err=<nil>
"faylgpj.exe" → "faylgpj.exe" err=<nil>
"satr\x00kesildi\x1b[31m" → "satrkesildi[31m" err=<nil>
"O‘zbekiston" → "O‘zbekiston" err=<nil>
"\xff\xfe" → "" err=matn UTF-8 formatida emas
"avatar.png" → ochildi
"../../etc/passwd" → rad etildi: path escapes from parent
"/etc/passwd" → rad etildi: path escapes from parent
- Ko‘rinmas belgilar va boshqaruv belgilari olib tashlandi.
\x1bbelgisi olib tashlangach, qolgan[31mendi oddiy matn va terminalga ta’sir qilmaydi. - Buzilgan UTF-8 rad etildi.
os.Rootkatalogdan tashqariga chiqishga urinishlarni to‘xtatdi.
Xulosa
- Kiruvchi ma’lumotning barchasi ishonchsiz. Bunga sarlavhalar, cookie’lar va fayllar ham kiradi.
- Kirishda validatsiya (allowlist) va normallashtirish, chiqishda esa joyga mos ekranlash qilinadi.
- "Xavfli belgilarni olib tashlash" universal himoya emas. Xavf ma’lumot qayerga borishiga bog‘liq.
- Boshqaruv va ko‘rinmas belgilarni tozalang, uzunlikni belgilarda o‘lchang.
- Fayl yo‘llari uchun
os.Root, buyruqlar uchun shell’sizexec.Command, loglar uchunslogishlating. - Yuklangan fayl turini tarkibiga qarab aniqlang va nomini server tomonida yarating.
- Boy matn uchun
bluemondaykabi tayyor sanitizer ishlating.
Keyingi darsda parolni tanlab topishga qaratilgan brute-force hujumlaridan himoyalanishni ko‘ramiz.