Tarkibga o'tish

Brute-force himoya

Brute-force (qo‘pol kuch) hujumi — parolni taxmin qilib, ketma-ket sinab ko‘rish. Hujumchi login endpointiga soniyasiga yuzlab so‘rov yuboradi: 123456, password, qwerty, aziza2000 va hokazo. Himoya bo‘lmasa, oddiy parollar bir necha daqiqada topiladi.

Parollarni xeshlash darsida bcrypt o‘g‘irlangan bazadagi xeshlarni ochishni qiyinlashtirishi aytilgan edi. Lekin u login endpointi orqali qilingan hujumdan himoya qilmaydi. Bu yerda hujumchi xeshni emas, to‘g‘ridan-to‘g‘ri serverning o‘zini sinaydi.

Hujum turlari

Hujum Qanday ishlaydi Belgisi
Klassik brute-force Bitta akkauntga ko‘p parol Bitta email, ko‘p muvaffaqiyatsiz urinish
Password spraying Ko‘p akkauntga bitta mashhur parol Ko‘p email, har biriga 1–2 urinish
Credential stuffing Boshqa saytdan sizib chiqqan email va parol juftlari Ko‘p email, har biri boshqacha, ko‘pincha turli IP’lardan

Har bir hujum turi boshqacha himoya talab qiladi. Faqat email bo‘yicha cheklov qo‘yilsa, password spraying uni chetlab o‘tadi. Faqat IP bo‘yicha cheklov esa minglab IP manzilli botnet’larga qarshi ojiz qoladi. Shuning uchun cheklovlar bir nechta kalit bo‘yicha qo‘yiladi.

Himoya strategiyasi

  1. Akkaunt bo‘yicha cheklov. Bitta email uchun muvaffaqiyatsiz urinishlar sanaladi. Chegaradan oshganda akkaunt vaqtincha bloklanadi va har safar bloklash muddati oshib boradi.
  2. IP bo‘yicha cheklov. Bitta IP manzildan kelayotgan login urinishlari cheklanadi. Bu password spraying’ga qarshi yordam beradi.
  3. Bir xil javob. "Email yo‘q" va "parol noto‘g‘ri" holatlari ajratilmaydi. Bu Login / Signup darsida ko‘rilgan.
  4. Qo‘shimcha choralar: ikki bosqichli autentifikatsiya, sizib chiqqan parollarni tekshirish va monitoring.

Nega butunlay bloklamaslik kerak?

"5 marta xato bo‘lsa, akkaunt administrator ochmaguncha bloklanadi" qoidasi xavfsiz ko‘rinadi. Lekin uning jiddiy kamchiligi bor: hujumchi har qanday foydalanuvchini ataylab bloklab qo‘yishi mumkin. Buning uchun uning emailiga 5 ta noto‘g‘ri parol yuborish kifoya. Bu xizmat ko‘rsatishni rad etish (DoS) hujumining bir turi.

Shuning uchun doimiy bloklash o‘rniga vaqtinchalik va o‘sib boruvchi kechikish ishlatiladi:

1–4-xato:   cheklovsiz
5-xato:     1 daqiqa kutish
6-xato:     2 daqiqa
7-xato:     4 daqiqa
...
maksimum:   1 soat
Muvaffaqiyatli login → hisoblagich nolga tushadi

Egasi bir necha daqiqadan keyin baribir kira oladi. Hujumchi uchun esa soatiga bir necha urinish parolni topishga yetmaydi.

Amalga oshirish

LoginGuard

type attempt struct {
    failures    int
    lockedUntil time.Time
    lastSeen    time.Time
}

type LoginGuard struct {
    mu        sync.Mutex
    attempts  map[string]*attempt
    threshold int           // nechta xatodan keyin bloklash boshlanadi
    baseDelay time.Duration // birinchi bloklash muddati
    maxDelay  time.Duration // eng uzun bloklash
    now       func() time.Time
}

Har bir kalit uchun muvaffaqiyatsiz urinishlar soni va bloklash tugaydigan vaqt saqlanadi. Kalit sifatida email:[email protected] yoki ip:203.0.113.7 ishlatiladi.

// Check: kalit bloklanganmi? Bloklangan bo‘lsa, qancha kutish kerakligini qaytaradi.
func (g *LoginGuard) Check(key string) (time.Duration, bool) {
    g.mu.Lock()
    defer g.mu.Unlock()

    a, ok := g.attempts[key]
    if !ok {
        return 0, true
    }
    if wait := a.lockedUntil.Sub(g.now()); wait > 0 {
        return wait, false
    }
    return 0, true
}

// Fail: muvaffaqiyatsiz urinishni qayd etadi.
func (g *LoginGuard) Fail(key string) {
    g.mu.Lock()
    defer g.mu.Unlock()

    a, ok := g.attempts[key]
    if !ok {
        a = &attempt{}
        g.attempts[key] = a
    }
    a.failures++
    a.lastSeen = g.now()

    if a.failures >= g.threshold {
        // 5-xato → base, 6-xato → 2×base, 7-xato → 4×base ...
        shift := min(a.failures-g.threshold, 16)
        delay := min(g.baseDelay<<shift, g.maxDelay)
        a.lockedUntil = g.now().Add(delay)
    }
}

// Success: muvaffaqiyatli logindan keyin hisoblagich tozalanadi.
func (g *LoginGuard) Success(key string) {
    g.mu.Lock()
    defer g.mu.Unlock()
    delete(g.attempts, key)
}

`g.baseDelay<