Güvenlik Pratikleri

1.9k görüntülenme Markdown

Bu platformun sağladığı güvenlik katmanları ve her birini devreye sokan çağrı. Yanında yayına çıkmış üç hata. Sürekli açık kalan bir kapı, koruduğu değeri yok eden bir filtre, çalıştırılabilir işaretleme olarak sunulan bir yükleme.

Genel Bakış

Aşağıdaki her katman bu kod tabanında var, adı belli tek bir çağrıyla devreye girer ve adı belli tek bir yerde uygulanır. Hiçbiri kendiliğinden çalışmaz. Bir controller kendi girdisini filtrelemez, açık bir form isteyene kadar korunmaz.

Üçüncü taraf kodu en sık bu iki katmanda yanılır. Girdi filtreleme alan ve tip başına olur. Müşteri API'sinde sahiplik istek başına değil, sorgu başına olur.

Ön Koşullar

  • Filtre sözlüğünün tamamı için Kullanıcı Girdisini Filtreleme. Bu makale yanlış filtreyi seçmenin sonuçlarını anlatır.
  • Güvenli hâle getirilecek bir ekran ve ona kimin ulaştığı. Bir admin operation'ı, açık bir form, bir müşteri ucu ve bir modül ucu farklı katmanları devreye sokar.
  • Başka bir sekmede açık duran panel Güvenlik ayarları. Aşağıdaki her eşik bir yapılandırma anahtarı. Modülünüze sabit yazılmış bir sınır, operatörün ayarını yok sayar.

Yapı

Katmanlar

KatmanNeyi durdururDevreye sokan
Girdi filtrelemeBir alanın içine sokuşturulan enjeksiyon ve işaretlemeKodunuz, alan başına tek çağrı, alana uyan tiple
Çıktı kodlamaSaklanmış değerler geri gösterilirken çalışırAdmin şablonlarında ve JSON'da kodunuz. Website şablonları kendiliğinden kaçırır
Yetki kontrolüGiriş yapmış bir operatörün rolünün izin vermediği bir şeyi yapmasıOperation sarmalayıcısı, operation'ın kendi bildiriminden. Yedek kanca cevap veriyorsa kodunuz
API kapsamıGeçerli bir kimlik bilgisinin kendisine verilmemiş bir uca ulaşmasıÇekirdek, rota yalnız-kimlik bayrağını kurmadıysa; kurduysa geçerli her anahtar geçer
Sahiplik kapsamıBir müşterinin başka bir müşterinin satırlarını okumasıHer sorguda kodunuz, enjekte edilen sahipten, istek gövdesinden asla
Form kapılarıSiteler arası gönderim, sel, botlar ve spam içeriğiKodunuz, sabit sırayla dört ya da beş çağrı, artı iki şablon etiketi

Adım Adım

Girdiyi Güvenle Okuyun

  1. Bir süper globale asla dokunmayın. Kaynak önekiyle filtre yardımcısından okuyun.
  2. Filtre tipini alışkanlıktan değil, alanın ne olduğundan seçin.
  3. Filtrelemek doğrulamak değildir. Tip filtresi ait olamayacakları çıkardıktan sonra kalanın kullanılabilir olduğunu kontrol edin.
  4. Her dizi değerini varsayılanla okuyun ve aynı ifade içinde tip dönüşümü yapın. Döngüdeki eksik anahtar her turda bir disk yazımına yol açar.
  5. Varlık kontrolünü false ile yazın, null ile asla.

Çağıranı Yetkilendirin

  1. Normal bir admin operation'ında yetki listesini operation'ın kendi özelliklerinde bildirin. Sarmalayıcı, metodunuz çalışmadan önce onu kontrol eder.
  2. Yedek kancanın cevapladığı operation'da yetkiyi kendiniz, dinleyicide ilk iş olarak kontrol edin. Sarmalayıcı zaten reddetmiş, ona ait hiçbir şey geçerli değil. Ne yetki listesi, ne demo kipi kapısı, ne de AJAX-dışı isteği reddeden kontrol.
  3. Bir modül API rotasında kapsam ile yalnız-kimlik bayrağı arasında bilinçli seçin. Yalnız-kimlik, doğru hedef kitleden geçerli her kimlik bilgisini içeri alır. Serbest ekran için makul, yazan hiçbir şey için değil.
  4. Müşteriye dönük her uçta kimliği enjekte edilen sahipten alın. İstek gövdesindeki bir hesap kimliği çağıranın önerisi, cevabı değil.

Açık Bir Formu Koruyun

  1. Jeton etiketini form öğesinin içine, captcha etiketini de formun captcha isteyebildiği yere koyun.
  2. Jetonu diğer kapılardan önce, demo kapısının ardından doğrulayın. Doğrulama varsayılan olarak AJAX başlığını bekler. Normal gönderim yapan form üçüncü argümanla AJAX-dışı bayrağını geçer.
  3. Önce sert bloğu, sonra captcha zorunluluğunu kontrol edin. Spam kapısı en sonda, alanlar okunduktan sonra gelir.
  4. İsteği sonda sayın ve captcha çözülüp çözülmediğine göre kalkan penceresini temizleyin ya da kaydedin.
  5. Formunuzu spam kapısının çağrı listesine ekleyin. Bunu atlayan bir misafir sayfasında dört bot katmanı var ama içerik kuralları yok.

Yüklenen Dosyayı Sunun

  1. Yardımcı üzerinden akıtın. İçerik tipi başlığını tespit edilen tipten kendiniz asla yazmayın.
  2. Yükleme dizinine doğrudan erişimi bir kural dosyasıyla kapatın.
  3. Satır içinde gösterilmesi gereken görsel dizinlerine dokunmayın.
  4. Üçünü de doğrulayın. Statik yol yasak döner, görsel yolu düzgün cevaplar. Controller ucu işaretlemeyi indirmeye zorlar, düz metni satır içinde gösterir.

Referans

Girdiyi Okumak

coremio/classes/Filter.php
// $arg, "SOURCE/key" ya da "SOURCE/key/subkey" olur; kaynaklar: GET/ POST/ REQUEST/ FILES/ SERVER/
// $mod filtre tipidir; $special bazı tiplerin izinli karakter kümesine karakter ekler.
public static function init($arg = NULL, $mod = false, $special = false);

// Tek bir kaynağa ham erişim. İç içe anahtarlar eğik çizgiyle. Olmayan anahtar => false.
public static function GET($arg = '');
public static function POST($arg = '');
public static function REQUEST($arg = '');
public static function SERVER($arg = '');

// İzinli etiket sıyırma; "hclear" tipinin kullandığı ve doğrudan da çağrılabilen metot.
public static function html_clear($arg = NULL, $allow = '');
TipOndan geriye ne kalırNe için kullanılır
passwordHer şey. Argümanı dokunmadan döndüren bilinçli bir geçirgenlikHer türden sır. Başka her tip, onları güçlü kılan karakterleri siler
hclearEtiketleri sıyrılmış metinBaşka bir biçimi olmayan serbest metin: bir not, bir konu satırı, bir ad
rnumbersBir tam sayıKayıt kimlikleri, sayılar, birazdan bir sorguya koyacağınız her şey
numbersRakamlar ve tire, dize olarakTelefon numaraları ve referans kodları, baştaki sıfırın önemli olduğu yerler
amount, rateAyraçlı rakamlar ve sırasıyla bir ondalık sayıPara ve yüzdeler. Kaçak bir harfi elinde tutan düz metin filtresi asla
emailYalnız adres karakter kümesiAdresler, üstüne bir de biçim kontrolü: filtre çıkarır, doğrulamaz
ipAdres karakterleriAdresler ve aralıklar. Bir şey karşılaştırmadan önce gerçek bir ayrıştırma yapın
routeHarf, rakam, tire, alt çizgi ve nokta; üst dizin geçişi önce silinirBir yolun ya da rota anahtarının parçası olacak her şey
letters_numbersHarf ve rakamlar, artı üçüncü argümanın izin verdikleriBiçimini sizin belirlediğiniz tanımlayıcılar: veritabanı adı, modül anahtarı, slug
domainHarf-rakam, nokta ve tire; küçük harfe çevrilir. Doğrulama yapmaz: not a domain!!! girdisi notadomain olarak geri dönerAlan adları, üstüne gerçek bir kontrol koyarak; tıpkı adres filtresi gibi. Üçüncü argümanı da yok sayar
Olmayan anahtar false döner, yani null'a karşı yazılmış kapı hep açıktır

Hem tipli okuyucu hem doğrudan kaynak okuyucuları, olmayan anahtarda false döner. null asla dönmezler. Null'a eşit değil koşulu her istekte doğru, arkasındaki dal koşulsuz çalışır. Bu yayına çıktı. Böyle korunan bir kip işareti her ziyarette bir simülasyon katmanı yükledi. Hiçbir şey sunucuya ulaşmadı, yanıtlar yine de başarılı göründü. Boş dizeyle karşılaştırmadan önce dizeye çevirin.

Yetkilendirme

imzalar
// coremio/helpers/admin.php - oturumdaki operatör yetkiyi taşıyorsa true.
public static function isPrivilege($privileges): bool;

// coremio/api/Auth/Scope.php - kimlik bilgisinin izinleri, rotanın gereksinimine karşı.
public function __construct(array $granted);
public function allows(string $required): bool;

// coremio/api/Resources/Client/_ClientResource.php - isteğin adına hareket ettiği müşteri.
protected function owner(): int;
protected function assertOwned(mixed $row, string $message = 'Resource not found.'): array;
Admin::isPrivilege() Normal bir operation'ın bildirdiği diziyi alır. Orada sarmalayıcı onu sizin için çağırır ve metodunuz koşmadan reddeder. Yedek kanca dinleyicisinde kimse çağırmaz, siz çağırırsınız.
Scope::allows() İzinler Grup/Eylem dizeleridir ve üç şekilde eşleşir: birebir dize, sondaki joker ile tüm grup ya da her şey anlamına gelen tek joker. Boş bir gereksinim geçer: kurulmamış bir rota kapsamı açık bir rotadır.
owner() Adına hareket edilen müşteri; çekirdek tarafından dış çağrıda kimlik bilgisinden, iç çağrıda girdiden enjekte edilir. Sıfır döndürmek yerine hata fırlatır; hiçbir sorgu sessizce kapsamsız çalışmaz.
assertOwned() Hem "yok" hem "başkasına ait" durumunu aynı bulunamadı cevabına çevirir. Ayrım bilginin kendisidir: sızdırmak, çağıranın hangi kimliklerin gerçek olduğunu saymasına izin verir.

Açık Bir Formu Kapıya Almak

Bağımsız beş katman, bu sırayla. İlk dördü kimin sorduğunu, beşincisi ne gönderdiğini yargılar.

SıraÇağrıHayır dediğinde
1Validation::verify_csrf_token($token, $key, $nonAjax = false)Başka hiçbir iş yapılmadan hemen reddedin. Anahtar, şablonun bastığıyla aynı olmalı
2ProcessRestriction::blocked($action)Bu adres sert blok penceresinin içinde. Hız sınırı mesajıyla cevaplayın ve durun
3Captcha::enabled($area) ya da BotShield::triggered($action)Captcha gerekiyor. Denemeyi kaydedin ve captcha gerekli durumuyla cevaplayın
4Validation::spam_guard($subject, $message, $email, $phone, $ip, $domain)Boş olmayan sonuç engellendi demektir. Dönen gerekçe loga gider, ziyaretçiye asla
5ProcessRestriction::hit($action) artı kalkanı kaydet ya da temizleAsıl işten sonra. Bunu atlamak, kurulmuş görünen bir sınırın neden hiç ateşlenmediğidir
imzalar
// coremio/classes/Validation.php
public static function get_csrf_token($form_index = '', $input = true);
public static function verify_csrf_token($incoming_data = '', $form_index = '', $nonAjax = false);
public static function spam_guard(string $subject = '', string $message = '', string $email = '',
                                  string $phone = '', string $ip = '', string $domain = ''): string;
public static function password_chars_error($password = ''): string;

// coremio/helpers/processrestriction.php - null ip "çağıranınki" demek, proxy'yi bilir.
public static function blocked(string $action, ?string $ip = NULL): bool;
public static function hit(string $action, ?string $ip = NULL): bool;
public static function clear(string $action, ?string $ip = NULL): void;

// coremio/helpers/botshield.php - uyarlanabilir captcha, adres başına sayılır.
public static function active(string $action): bool;
public static function triggered(string $action, ?string $ip = NULL): bool;
public static function record(string $action, ?string $ip = NULL): void;
public static function clear(string $action, ?string $ip = NULL): void;

// coremio/helpers/captcha.php - sabit ayar ve cevap kontrolü.
public static function enabled(string $area = ''): bool;
public function check(): bool;

// coremio/classes/FraudModule.php - yalnız sipariş anında. Boş dize temiz demektir.
public static function run_checks(array $params = []): string;
spam_guard() gerekçe döner, boolean değil Boş dize temiz demektir; başka her şey, engellenenler listesine zaten yazılmış, operatöre dönük gerekçedir. Ziyaretçiye bunun yerine genel çevrilmiş mesajı gösterin. Gerekçe hangi kuralın ateşlendiğini söyler, yani saldırgan için bir ayar kılavuzudur.
Jeton anahtarı birebir bir sözleşmedir Şablonun bastığı dize ile işleyicinin doğruladığı dize aynı olmak zorundadır. Rotadan türetilmezler, yani uyumsuzluk göreceğiniz bir hata değildir. Her gönderim doğrulamadan geçemez.
Eşikler yapılandırmada yaşar Deneme sayıları, pencereler ve blok süreleri, Güvenlik ekranlarının yazdığı seçenek dosyasında operatörün sahibi olduğu ayarlardır. Modülünüze yazılmış bir sayı, operatör kendi ayarını değiştirdiği anda çalışmayı bırakır.
Sahtekârlık kontrolleri ayrı bir katmandır Sipariş oluşturulurken, hiçbir şey kalıcı hâle gelmeden koşarlar ve spam kapısı değildirler. Hata fırlatan bir modül geçti sayılır ve loglanır, yani bir sağlayıcı kesintisi ödeme akışını götüremez.

Çıktı ve Dosyalar

imzalar
// coremio/classes/Utility.php - her yanıtın içinden geçtiği JSON kodlayıcı.
public static function jencode($string = '', $flags = 0): string|false;
public static function jdecode($string = '', $mode = false);

// coremio/classes/Utility.php - saklı bir dosyayı tarayıcıya vermenin desteklenen tek yolu.
public static function stream_uploaded_file(string $diskPath, string $fileName, array $opt = []): void;
stream_uploaded_file() Satır içinde yalnız sabit ve etkisiz bir küme için sunar: taşınabilir belgeler, dört raster görsel tipi ve düz metin. İşaretleme ve vektörel görseller dahil geri kalan her şey indirmeye zorlanır; genel bir tip, koklama-yok başlığı ve kum havuzu politikası taşır. Dosya adından satır sonlarını ve tırnakları siler, içeriği gönderir ve çıkar.
Önbellek seçeneği yalnız içerikten türeyen adresler içindir İsteğe bağlı üçüncü argüman önbellek politikasını ezer; dosya değiştiğinde değişen adresler içindir. Hassas bir belge onu geçmemelidir: kısa ve özel varsayılan meselenin kendisidir.
Utility::jencode() Ham kodlayıcı yerine kullanılır, böylece her yanıt tek bir seçenek kümesini paylaşır. Latin dışı karakterler ve eğik çizgiler kaçırılmak yerine okunur kalır. Kodlamak kaçırmak değildir: işaretlemenin içine basılacak bir değer, basıldığı noktada yine kaçırılmalıdır.
htmlspecialchars() Admin şablonları otomatik kaçırma yapmayan düz PHP'dir, yani oraya bastığınız her şey basıldığı noktada kaçırılır. Website şablonları varsayılan olarak kaçırır, yani ters tuzak. Oraya önceden kodlanmış bir değer saklamak onu iki kez kodlar ve ekranda varlıkları gösterir.

Örnek

Baştan sona bir misafir formu. Şablon iki etiketi gösterir, işleyici beş katmanı sırayla uygular.

şablon tarafı
<form action="https://dev.wisecp.com/tr/acme-request" method="post" data-results="#acme-out">
    <input type="text"  name="company">
    <input type="email" name="email">
    <input type="password" name="panel_password">
    <textarea name="note"></textarea>

        
    
</form>
işleyici tarafı
public function submit(\Operation $operation): bool
{
    // Sarmalayıcı her operation'a bir Operation nesnesi verir. Tanıtım kapısıyla açın:
    // bir tanıtım sisteminde herhangi bir yazma gerçekleşmeden önce fırlatır.
    $operation->demo();

    // 1. Jeton, diğer her kapıdan önce. AJAX olmayan gönderimde üçüncü argüman true.
    if (!\Validation::verify_csrf_token((string) Filter::init('POST/token', 'hclear'), 'acme-request'))
        return $operation->output(['status' => 'error', 'message' => Language::g('needs/csrf-failed')]);

    // 2. Bu adres için sert engel penceresi.
    if (\ProcessRestriction::blocked('acme-request'))
        return $operation->output(['status' => 'error', 'message' => Language::gc('acme/too-many')]);

    // 3. Captcha: operatörün sabit ayarı YA DA uyarlanabilir kalkanın devreye girmesi.
    $needCaptcha = \Captcha::enabled('acme-request') || \BotShield::triggered('acme-request');

    if ($needCaptcha && !(new \Captcha())->check()) {
        \BotShield::record('acme-request');
        return $operation->output(['status' => 'captcha_required']);
    }

    // Alan başına bir çağrı ve tip, alanın NE OLDUĞUNA göre seçilir.
    // Sır, geçirgen filtreyi kullanır: herhangi bir metin filtresi, tam da parolayı
    // güçlü kılan noktalama işaretlerini sessizce silerdi.
    $company  = Filter::init('POST/company', 'hclear');
    $email    = Filter::init('POST/email', 'email');
    $note     = Filter::init('POST/note', 'dtext');
    $password = Filter::init('POST/panel_password', 'password');

    // Filtreleme oraya ait olamayacakları çıkardı; kalanın kullanılabilir olup olmadığına doğrulama karar verir.
    if (!filter_var($email, FILTER_VALIDATE_EMAIL))
        throw new Exception(Language::gc('acme/email-invalid'));

    if ($e = \Validation::password_chars_error($password)) throw new Exception($e);

    // 4. İçerik ve gönderen kuralları; alanlar okunduktan sonra, asıl işten önce.
    if (\Validation::spam_guard('', $note, $email, '', UserManager::GetIP()) !== '')
        throw new Exception(Language::g('needs/spam-blocked'));

    // ... asıl iş

    // 5. Bu isteği sayın ve kalkan penceresini sonuçlandırın.
    \ProcessRestriction::hit('acme-request');
    if ($needCaptcha) \BotShield::clear('acme-request');
    else              \BotShield::record('acme-request');

    return $operation->output(['status' => 'successful']);
}

Diğer iki ekran, kapıyı kaçınılmaz kılan biçimde.

sahiplik kapsamı ve dosya sunumu
public function GetNote(array $body = []): array
{
    $id = (int) ($body['id'] ?? 0);

    // Sahip, kimlik bilgisinden gelir. Gövdedeki bir hesap kimliği çağıranın ÖNERİSİDİR
    // ve hesabı seçmek için asla kullanılmaz.
    $row = WDB::select('*')->from('acme_notes')
              ->where('id', '=', $id)
              ->where('owner_id', '=', $this->owner())
              ->build('assoc');

    // Olmayan da yabancı da 404 döner: hangi kimliklerin var olduğu başlı başına bilgidir.
    return ['data' => $this->assertOwned($row)];
}

public function stream_note_file(int $id): void
{
    $row = $this->model->note($id);
    if ((int) ($row['owner_id'] ?? 0) !== $this->owner()) { http_response_code(404); exit; }

    // Bu başlıkları asla tespit edilen tipten elle kurmayın: satır içinde sunulan,
    // yüklenmiş bir sayfa bu köken üzerinde, onu açan sonraki operatöre karşı çalışır.
    \Utility::stream_uploaded_file(ROOT_DIR . $row['path'], $row['name']);
}
resources/uploads/acme-notes/.htaccess
# Yukarıdaki sahiplik kontrolü PHP'de yaşar; web sunucusunun ondan haberi yoktur ve
# aynı baytları doğrudan yoldan sunar. Dosyayı diskten okumak bir dosya sistemi
# işlemidir ve etkilenmez, yalnız doğrudan HTTP yolu kapanır.
# Her iki söz dizimi de yazılır; ağaçtaki her özel yükleme dizini zaten böyle yazılmıştır:
# 2.4 yönergesi tek başına, mod_authz_core olmayan bir sunucuda hata verir.
<IfModule mod_authz_core.c>
    Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
    Order allow,deny
    Deny from all
</IfModule>

Tuzaklar

Genel bir temizleyici ilk olarak sır alanlarını bozar

Metin filtreleri noktalama işaretlerini çıkarır, parolayı güçlü kılan şey de noktalama. Birini bir parola alanında çalıştırın, değer sessizce kısalır. Hesap, müşterinin hiç yazmadığı bir değerle açılır, arıza sonradan giriş hatası olarak çıkar. Alan başına filtreleyin, sırlara geçirgen tipi verin.

Web'den erişilebilen yükleme dizininde erişim kontrolü yok

Controller'ınızdaki her sahiplik kontrolü, dosya yolu doğrudan istenerek atlanır. Sunucu yüklenmiş bir sayfayı sayfa olarak sunar. İki yarı da gerekli. Akış yardımcısı tipi etkisiz hâle zorlar, ret kuralı statik yolu kapatır.

Boşluk testleri saklanmış bir sıfırı yok sayar

Boşluk testi sıfır dizesi için doğru sonuç verir. Operatörün açıkça kapattığı bir ayar, hiç yapılandırılmamış gibi okunur ve kod varsayılanına düşer. Anahtarı varsayılanla okuyup aynı ifadede tip dönüşümü yapın, sonra karşılaştırın.

Çağıranın verdiği bir adrese asla istek atmayın

Çağıranın seçtiği bir adrese giden dış istek, içerik ağının arkasındaki origin'i adres sahibine ifşa eder. Müşteriye dönük uçlar bunun yerine yüklenen baytları alır. Bir adresi ancak operatör ekranında ya da sağlayıcının imzaladığı bir kaynaktan getirin.

Faydalı oldu mu?

Geri bildiriminiz için teşekkürler!

Hâlâ Yardıma mı İhtiyacınız Var?

Yukarıda bulamadığınız her şey için destek ekibimiz her zaman yanınızda.