Veritabanında Kullanılan Hatalı Hash Yöntemlerinin SAST Araçlarıyla Tespiti ve Güvenli Alternatifler

Büyük veri ihlalleri ve sızıntıları gibi olaylar sıkça gündemde. Ele geçirilen parola hashleri, zayıf algoritma ve tekniklerle oluşturulduysa, saldırganlar parolaların açık haline çok daha kolay ve hızlı bir şekilde ulaşabilir. Bu yazıda, kriptografik olarak zayıf hashleme algoritmaları, yetersiz salt kullanımı nedeniyle ortaya çıkan güvenlik açıklarını SAST araçlarıyla nasıl tespit edebileceğimizi ve zafiyetli kodu nasıl güvenli hale getirebileceğimizi inceleyeceğiz.

Aşağıda python dilinde yazılmış zayifetli bir kod örneği görüyoruz. Burada yaygın olarak yapılan iki hataya örnek olarak, iki salt fonksiyonumuz var. Daha sonra bu elde edilen salt’la, parola birleştirip zayıf bir hash algoritması kullanılarak, şifre kaydediliyor. Bu güvenli olmayan yöntemleri teker teker inceleyelim.

import hashlib
import random

def generate_weak_salt():
    return str(random.randint(1000, 9999))

def generate_user_salt(username):
    return hashlib.sha1(username.encode()).hexdigest()

def vulnerable_register_weak_salt(request):
    username = request.GET['username']
    password = request.GET['password']
    
    sha1 = hashlib.sha1()
    
    weak_salt = generate_weak_salt()

    salted_password = password + weak_salt
    sha1.update(salted_password.encode())
    
    password_hash = sha1.hexdigest()
    
    store(username, password_hash, weak_salt, "weak_salt")
    
    return "User registered successfully"

def vulnerable_register_user_salt(request):
    username = request.GET['username']
    password = request.GET['password']
    
    sha1 = hashlib.sha1()
    
    user_salt = hashlib.sha1(username.encode()).hexdigest()

    salted_password = password + user_salt
    sha1.update(salted_password.encode())
    
    password_hash = sha1.hexdigest()
    
    store(username, password_hash, user_salt, "user_salt")
    
    return "User registered successfully"

def store(username, password_hash, salt, salt_type):
    print(f"Storing {username} with hash {password_hash}, {salt_type} {salt}")

class MockRequest:
    GET = {
        'username': 'user1',
        'password': 'mypassword'
    }

request = MockRequest()
vulnerable_register_weak_salt(request)
vulnerable_register_user_salt(request)

Bilindiği üzere salt, aynı parolaya sahip iki kullanıcının parola hash’lerinin aynı olmasını engeller. Böylece saldırganlar hazır hash tabloları (rainbow table) kullanarak şifreleri hızlıca bulamazlar. Ama oluşturulan salt’lar tahmin edilebilir olduğunda, bahsettiğimiz bu problemi çözmüş olmaz, çünkü saldırganlar bu tahmin edilebilir salt’larla kendilerine bir hash veritabanı oluşturup, bir veri ihlali olduğunda bu veritabanı üzerinden arama yapıp, açık parolalara ulaşabilirler.

def generate_weak_salt():
    return str(random.randint(1000, 9999))

Yukarıdaki fonksiyonu incelediğimizde, kullanılan randint fonksiyonunun kriptografik olarak yeterince rastgele olmamasının yanı sıra, seçilen sayı kümesi çok dar. Bu da oluşturulan salt’ları tahmin edilebilir hale getirir.

def generate_user_salt(username):
    return hashlib.sha1(username.encode()).hexdigest()

Bu fonksiyonda salt kullanıcı adını hash’leyerek oluşturuluyor. Kullanıcı girdisi üzerinden oluşturulan salt’lar, bu girdiyi bilen bir saldırgan tarafından kolayca hesaplanabilir. Bu da oluşturulan salt’ları tahmin edilebilir hale getirir.

salted_password = password + user_salt
sha1.update(salted_password.encode())

password_hash = sha1.hexdigest()

Son olarak, salt eklenmiş parola oluşturulurken zayıf bir hashleme algoritması olan SHA1 kullanıyor. Burada kullanmamız gereken algoritmada aramamız gereken özellikler, karmaşıklık, hesaplama süresinin fazla olması ve salt’u kendisinin otomatik olarak yönetebiliyor olması. Günümüzde bunun için en iyi adaylar Argon2 ve bcrypt.

Şimdi kodu bir SAST aracı olan Fortify’la tarayıp, bu tip zafiyetleri otomatize olarak nasıl tespit edebiliriz, inceleyelim.

randint fonksiyonunu, Insecure Randomness bulgusu altında yakalayabildik.

Kullanıcı girdisi üzerinden oluşturulan salt, Weak Cryptographic Hash: User-Controlled Salt bulgusu altında tespit edilebildi.

Son olarak, zayıf bir hash algoritması olan SHA1 kullandıldığını tespit ettik. Şimdi Argon2 algoritması kullanarak kodu güvenli hale getirelim.

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher()

def secure_register(request):
    username = request.GET['username']
    password = request.GET['password']
    
    password_hash = ph.hash(password)
    
    store(username, password_hash)
    
    return "User registered successfully"

def store(username, password_hash):
    print(f"Storing {username} with hash {password_hash}")

def verify_password(stored_password, provided_password):
    try:
        return ph.verify(stored_password, provided_password)
    except VerifyMismatchError:
        return False

class MockRequest:
    GET = {
        'username': 'user1',
        'password': 'mypassword'
    }

request = MockRequest()
secure_register(request)

Burada ek olarak bir salt oluşturmadık, çünkü Argon2 bizim için rastgele bir salt oluşturacak. Şimdi oluşan hash’i detaylı inceleyelim.

$argon2id$v=19$m=65536,t=3,p=4$IXt+4US+qLICLTQVjhEMBQ$FYNJM0L9YROvy1E/TQdkHaf1a7LUBpEPmJ1VS3ItKwE
  • m: Argon2 algoritmasının kullanacağı hafıza miktarını belirtir. Burada, 65536 KB (64 MB) kullanıldığını gösterir. Hafıza maliyeti, algoritmanın çalışması sırasında kullanılan bellek miktarını kontrol eder.

  • t :Algoritmanın kaç tur (iteration) çalıştırılacağını belirtir. Burada, algoritma 3 tur çalıştırılacaktır. Zaman maliyeti, hesaplama süresini kontrol eder ve brute force saldırılarına karşı koruma sağlar.

  • p :Paralellik derecesi, aynı anda kaç thread çalıştırılacağını kontrol eder ve hesaplama süresini etkiler.

  • Salt : IXt+4US+qLICLTQVjhEMBQ

  • Hash: FYNJM0L9YROvy1E/TQdkHaf1a7LUBpEPmJ1VS3ItKwE □

Written by

Furkan Turan