PHP kullanarak siteler arası istek sahteciliği (CSRF) belirteci nasıl düzgün şekilde eklenir?


100

Web sitemdeki formlara biraz güvenlik katmaya çalışıyorum. Formlardan biri AJAX kullanıyor, diğeri ise basit bir "bize ulaşın" formu. CSRF jetonu eklemeye çalışıyorum. Karşılaştığım sorun, jetonun yalnızca bazen HTML "değerinde" görünmesi. Geri kalan zamanlarda değer boştur. AJAX formunda kullandığım kod:

PHP:

if (!isset($_SESSION)) {
    session_start();
$_SESSION['formStarted'] = true;
}
if (!isset($_SESSION['token']))
{$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;

}

HTML

 <form>
//...
<input type="hidden" name="token" value="<?php echo $token; ?>" />
//...
</form>

Herhangi bir öneri?


Sadece merak, ne token_timeiçin kullanılır?
zerkms

@zerkms Şu anda kullanmıyorum token_time. Bir belirtecin geçerli olduğu süreyi sınırlayacaktım, ancak kodu henüz tam olarak uygulamadım. Açıklık adına, yukarıdaki sorudan çıkardım.
Ken

1
@Ken: Böylece kullanıcı bir formu açtığında vakayı alabilir, gönderebilir ve geçersiz jeton alabilir mi? (geçersiz olduğundan beri)
zerkms

@zerkms: Teşekkür ederim ama biraz kafam karıştı. Bana bir örnek verme şansın var mı?
Ken

2
@Ken: Tabii. Belirtecin saat 10: 00'da sona erdiğini varsayalım. Şimdi saat 09:59. Kullanıcı bir form açar ve bir jeton alır (bu hala geçerlidir). Ardından kullanıcı 2 dakika süreyle formu doldurur ve gönderir. Şu an saat 10:01 olduğu sürece - jeton geçersiz kabul ediliyor, bu nedenle kullanıcı form hatası alıyor.
zerkms

Yanıtlar:


304

Güvenlik kodu için lütfen belirteçlerinizi şu şekilde oluşturmayın: $token = md5(uniqid(rand(), TRUE));

Şunu deneyin:

CSRF Jetonu Oluşturma

PHP 7

session_start();
if (empty($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];

Not: İşverenimin açık kaynak projelerinden biri, arka porta random_bytes()ve random_int()PHP 5 projelerine yönelik bir girişim . MIT lisanslıdır ve Github ve Composer'da paragonie / random_compat olarak mevcuttur .

PHP 5.3+ (veya ext-mcrypt ile)

session_start();
if (empty($_SESSION['token'])) {
    if (function_exists('mcrypt_create_iv')) {
        $_SESSION['token'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
    } else {
        $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));
    }
}
$token = $_SESSION['token'];

CSRF Jetonunu Doğrulama

Sadece kullanmayın ==veya hatta ===kullanmayın hash_equals()(yalnızca PHP 5.6+, ancak karma uyumlu kitaplığa sahip önceki sürümlerde mevcuttur ).

if (!empty($_POST['token'])) {
    if (hash_equals($_SESSION['token'], $_POST['token'])) {
         // Proceed to process the form data
    } else {
         // Log this as a warning and keep an eye on these attempts
    }
}

Form Başına Jetonlarla Daha İleri Gitme

Kullanarak jetonları yalnızca belirli bir form için kullanılabilir olacak şekilde daha da kısıtlayabilirsiniz. hash_hmac() . HMAC, daha zayıf karma işlevlerle (örn. MD5) bile kullanımı güvenli olan özel bir anahtarlı karma işlevdir. Bununla birlikte, bunun yerine SHA-2 ailesinin karma işlevlerini kullanmanızı öneririm.

Önce, bir HMAC anahtarı olarak kullanmak için ikinci bir jeton oluşturun, ardından bunu oluşturmak için aşağıdaki gibi bir mantık kullanın:

<input type="hidden" name="token" value="<?php
    echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
?>" />

Ve sonra jetonu doğrularken uyumlu bir işlem kullanarak:

$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
if (hash_equals($calc, $_POST['token'])) {
    // Continue...
}

Bir form için üretilen belirteçler, bilinmeden başka bir bağlamda yeniden kullanılamaz $_SESSION['second_token']. HMAC anahtarı olarak sayfaya az önce bıraktığınızdan farklı bir belirteç kullanmanız önemlidir.

Bonus: Hibrit Yaklaşım + Dal Entegrasyonu

Twig şablonlama motorunu kullanan herkes , Twig ortamına bu filtreyi ekleyerek basitleştirilmiş bir ikili stratejiden yararlanabilir:

$twigEnv->addFunction(
    new \Twig_SimpleFunction(
        'form_token',
        function($lock_to = null) {
            if (empty($_SESSION['token'])) {
                $_SESSION['token'] = bin2hex(random_bytes(32));
            }
            if (empty($_SESSION['token2'])) {
                $_SESSION['token2'] = random_bytes(32);
            }
            if (empty($lock_to)) {
                return $_SESSION['token'];
            }
            return hash_hmac('sha256', $lock_to, $_SESSION['token2']);
        }
    )
);

Bu Twig işlevi ile hem genel amaçlı jetonları şu şekilde kullanabilirsiniz:

<input type="hidden" name="token" value="{{ form_token() }}" />

Veya kilitli varyant:

<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />

Twig yalnızca şablon oluşturma ile ilgilidir; yine de belirteçleri doğru şekilde doğrulamanız gerekir. Bana göre Twig stratejisi, maksimum güvenlik olasılığını korurken daha fazla esneklik ve basitlik sunuyor.


Tek Kullanımlık CSRF Jetonları

Her CSRF belirtecinin tam olarak bir kez kullanılmasına izin verilmesi gibi bir güvenlik gereksiniminiz varsa, en basit strateji onu her başarılı doğrulamadan sonra yeniden oluşturur. Ancak, bunu yapmak, aynı anda birden çok sekmeye göz atan kişilerle iyi karışmayan her önceki belirteci geçersiz kılar.

Paragon Initiative Enterprises , bu köşe vakalar için bir Anti-CSRF kitaplığı bulunduruyor . Yalnızca tek kullanımlık form başına belirteçlerle çalışır. Oturum verilerinde yeterli sayıda token depolandığında (varsayılan yapılandırma: 65535), ilk olarak en eski kullanılmayan tokenları devre dışı bırakır.


güzel, ancak kullanıcı formu gönderdikten sonra $ jetonunu nasıl değiştirebilirim? sizin durumunuzda, kullanıcı oturumu için kullanılan bir jeton.
Akam

1
Github.com/paragonie/anti-csrf'nin nasıl uygulandığına yakından bakın . Jetonlar tek kullanımlıktır, ancak birden fazla depolar.
Scott Arciszewski

@ScottArciszewski Oturum kimliğinden bir sır içeren bir ileti özeti oluşturmak ve ardından alınan CSRF belirteç özetini oturum kimliğini önceki sırrımla yeniden hash ederek karşılaştırmak hakkında ne düşünüyorsunuz? Umarım ne söylemek istediğimi anladın.
MNR

1
CSRF Belirtecinin Doğrulanmasıyla ilgili bir sorum var. $ _POST ['token'] boşsa, devam etmemeliyiz, çünkü bu gönderi talebi token olmadan gönderildi, değil mi?
Hiroki

1
Çünkü HTML formunda yankılanacak ve siz onun tahmin edilemez olmasını istiyorsunuz, böylece saldırganlar onu öylece taklit edemez. Burada gerçekten sorgulama-yanıt kimlik doğrulamasını uyguluyorsunuz, sadece "evet, bu form yasaldır" değil, çünkü bir saldırgan bunu taklit edebilir.
Scott Arciszewski

24

Güvenlik Uyarısı : md5(uniqid(rand(), TRUE))rasgele sayılar oluşturmanın güvenli bir yolu değildir. Daha fazla bilgi ve kriptografik olarak güvenli bir rastgele sayı oluşturucudan yararlanan bir çözüm için bu yanıta bakın .

Görünüşe göre if'inizle başka bir şeye ihtiyacınız var.

if (!isset($_SESSION['token'])) {
    $token = md5(uniqid(rand(), TRUE));
    $_SESSION['token'] = $token;
    $_SESSION['token_time'] = time();
}
else
{
    $token = $_SESSION['token'];
}

11
Not: md5(uniqid(rand(), TRUE));Güvenlik bağlamlarına güvenmem .
Scott Arciszewski

2

Değişken $token, oradayken oturumdan alınmıyor

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.