Güvenli Kodlama (Secure Coding)

Güvenli Kodlama Nedir, Neden Önemlidir?

Bizim bir yazılım projesi geliştirirken siber saldırıya davet oluşturabilecek yerleri tespit edip ona göre önlem alarak kodlarımızı yazmamız demektir. Siber saldırıların temel nedeni yazılımların geliştirilirken güvenlik önlemi alınmamasıdır. Genelde bir proje kodlama aşamasındayken güvenliği 2. plana atılıyor. Yani ilk önce proje bitsin sonra güvenlik de ele alınılır diye düşünülüyor. Bu da hem maddi hem de zaman kaybına sebep oluyor. Mesela biz bir projeyi geliştirmeye başladık diyelim ve kodlarımızı 3.parti bir kütüphaneye bağımlı yazdık. Geliştirme aşamasındayken bu kütüphanede bir zafiyet var mı yok mu diye kontrol etmedik çünkü güvenlikle ilgili kontrollerimizi biz proje bitimine ertelemiştik. Projemizin geliştirilmesi bittikten sonra kontrollerimizi yaptık ve bu kütüphanede ciddi bir güvenlik zafiyeti olduğunu ve henüz yamasının olmadığını keşfettik. Bu durumda bizi bekleyen birkaç senaryo var. Ya kütüphaneyi değiştireceğiz (bu da kodlarımızı revize etmemiz gerekecek demek oluyor) ya pentest ile zafiyetin etkilediği yerleri bulup ek önlemler almamız gerekiyor ya da kendi yamamızı geliştirmemiz gerekiyor. Her türlü maddi olarak da zaman olarak da bir kayıp oluşmuş olacak. Bu yüzden güvenliğin 2. plana atılmaması ve kodlamaya başlamadan önce de kodlama esnasında da kapsamlı olarak incelenmesi önemlidir. Peki kodlamaya başlamadan önce güvenliği nasıl ele alabiliriz gelin beraber bakalım.

Yukarıda pentestten bahsettim ama bu pentest ne demek? Pentest yani penetrasyon testi, sistemleri saldırganların bakış açısıyla bakarak zafiyet var mı diye test etmek demektir.


Tehdit Modelleme

Bir sistemin mimarisini, veri akışını ve potansiyel zafiyet oluşturabilecek yerlerini analiz ederek ekiplerin kodlarken nerelere dikkat etmeleri gerektiğini baştan değerlendirebilmelerini sağlayan modeldir. 6 aşamadan oluşmaktadır:

  • Varlıkların belirlenmesi(veritabanı tabloları, ödeme, ürün arama vs.)

  • Mimari incelemenin yapılması(sistemin hangi mimariyi kullanacağı)

  • Yazılımların parçalara bölünmesi(ödeme sayfasının ayrı, ürünler sayfasının ayrı incelenmesi)

  • Tehditlerin belirlenmesi

  • Belirlenen tehditler için dokümantasyon oluşturulması

  • Bulduğumuz her tehdit için değerlendirme yapılması(risk dereceleri, nasıl önlem alınabileceği)

En çok kullanılan tehdit modeli Microsoft'un STRIDE modelidir. Bu model bir uygulamanın veya sistemin CIA üçlüsünü karşılamasını sağlamayı amaçlar.

CIA nedir?

Bilgi güvenliğinin temel taşlarından olan gizlilik, bütünlük ve erişilebilirliğe biz CIA üçlüsü diyoruz. CIA, C: Confidentiality-Gizlilik, I:Integrity-Bütünlük, A:Availability-Erişilebilirlik kelimelerinin baş harflerinden oluşmaktadır.

Tehdit Modelleme Yapılabilecek Tool Önerisi

  • Microsoft Threat Modeling Tool

  • OWASP Threat Dragon

  • Cisco Vulnerability Management

  • ThreatModeler


Güvenli Kodlama Teknikleri Nelerdir?

Güvenli kodlama tekniklerini anlatmaya başlamadan önce güvenli kod yazmak ve siber saldırılardan korunmak için "Asla kullanıcıdan gelen girişlere güvenme" mottosunu benimsemeniz gerektiğini vurgulamak istiyorum :)

1. Giriş Doğrulaması

Girdinin nereden geldiği önemli değildir yani kaynak güvenilir de olsa güvensiz de bizim bu girdiyi doğrulamamız gerekiyor. Kullanıcının girdisinde veri uzunluğu(mesela telefon numarası girilen kısımda uzunluk ona göre sınırlandırılmalıdır), karakter seti(kullanıcının mail kısmına mail dışında ekstra karakter girişleri önlenmeli) kontrol edilmelidir. Buralara kısıtlamalar getirilmeli ve kod, script girdileri engellenmelidir. Aşağıdaki gibi bir girdinin kullanılabilmesine izin vermeyin(nasıl önlenebileceğini ilerleyen kısımlarda ele alacağız).

"><script>alert(1);</script>"@example.org

Bu kısıtlamalar ve kontroller ise sunucu tarafında yapılmalıdır çünkü unutulmamalı ki istemci tarafında alınan önlemler her zaman saldırganlar tarafından atlatılabilmektedir( JavaScript giriş doğrulamasını istemci tarafında yaptık diyelim, JavaScript'i devre dışı bırakan veya Web Proxy kullanan birisi kolaylıkla bunu bypass edebilecektir(atlatabilecektir) o yüzden sunucu tarafında kontrol edelim! :) )

2. Kimlik Bilgilerini ve Parolaları Yönetin

  • Kullanıcılardan gelen parolaları mutlaka güvenle saklayın. Bunun için parolalar veri tabanına hash'lenerek kaydedilmelidir. Hash'leyerek kaydetmek veri tabanındaki verilerin olası çalınma durumunda saldırgan tarafından okunmasını engeller. Hash'e ek olarak saltlamak ise bu parolaların aynı olması durumunda da benzersiz olarak saklanmasını sağlar. Bu sebeple beraber kullanıldığında daha iyi bir güvenlik sağlanacaktır. En çok kullanılan hash'leme yöntemleri SHA256 ve Bcrypt'dir. Bcrypt algoritması şifreleri hash'lemenin yanı sıra default olarak saltlama işlemi de yapmaktadır.

Hash ve saltlama için bir örneğe göz atalım.

Mesela merhaba kelimesini SHA256 ile hashlemek istedik.

4c6bcdd55f3153e1939669ab1ec039e4059174dc25abdfcb2f58868849b4d61b

Hash'imiz yukarıdaki gibi gözükecektir. Salt ise hash'lemeden önce, girilen parolanın başına veya sonuna rastgele oluşturulmuş bir metin eklemek demektir. Bu salt her seferinde değişmelidir. Ayrıca parola ile birlikte bu salt değerinin de veri tabanında tutulması gerekir çünkü parolanın doğruluğunu kontrol etmek için bizim bu salt değerine ihtiyacımız olacaktır.

Şimdi de merhaba kelimelisi önce saltlayalım sonra hash'leyelim. Salt değerimiz abcdefg12345 olsun. Bu saltı kelimenin sonuna ekleyelim.

Saltlanmış kelime: merhabaabcdefg12345

Bunu hashlersek sonucumuz,

ab7a4f45f02e3c8f10bfbc6ad25244c18ad3d84bfb6bf89648c595439fbaffe5

olacaktır. Her seferinde başka salt değeri atayarak bu hash'in bezersiz olmasını sağlayabiliriz.

  • Kullanıcıları güçlü şifreler koymaları için yönlendirin. Bunun için kullanıcı kayıt sayfasına parola gücü ölçer ekleyebilirsiniz. Böylelikle sık kullanılan veya önceden ihlal edilmiş parolaları kullandıkları zaman bunu uyarı olarak gösterebilir ve kullanıcının bu parolalar ile kaydolmalarını engelleyebilirsiniz. Bu uygulama için zxcvbn kütüphanesi veya pwned passwords api gibi servislerden faydalanabilirsiniz. Böylelikle brute force saldırılarının başarılı olmasını önemli ölçüde azaltabilirsiniz.

Brute Force nedir?

Hackerların kullanıcıların hesaplarını çalmak için parola ve kullanıcı adlarına çok sayıda farklı denemeler yaparak doğru olanı bulmaya çalışmasıdır. Bu sebeple parolalar karmaşık olursa hackerların bu denemelerde başarılı olmaları zorlaşacaktır. Biz de hackerların işlerini kolaylaştırmak istemeyiz :)

  • Kimlik bilgilerinizi hiçbir zaman uygulamanın komut dosyasına kaydetmeyin veya hashleyerek kaydedin. Bunun sebebi ise genelde insanlar bu tarz önemli bilgileri komut dosyalarından silmeyi unutuyor ve o şekilde Github'a yüklüyorlar. Bu da kimlik bilgilerinizin saldırganların eline geçmesi demek oluyor. Mesela Discord Webhook kullanarak projemizin çıktısını bot mesajı olarak göndermek için bir kod yazdık. Eğer biz kodlarımızı açık kaynaklı olarak paylaşmadan önce Webhook bilgilerimizi kodumuzun içerisinden silmezsek herkesin buraya bot mesajı yollamasına imkan vermiş oluruz. Bu da ciddi bir güvenlik riski doğuracaktır. Bununla ilgili Uber'in 2016 yılında yaşadığı durumu okumak için buraya tıklayınız.

3. Önce Verileri Temizleyin Sonra Diğer Sistemlere Gönderin

Giriş doğrulama kısmını anlatırken mail içerisine script yazılmasını nasıl önleyeceğimizi sonradan ele alacağımızı söylemiştim. Bunun için kullanıcıdan gelen verileri başka sistemlere yollamadan önce temizlememiz gerekmektedir. Bunu ise 3 şekilde yapabiliriz: White List(izin verilenler listesi) kullanarak, Black List(izin verilmeyenler listesi) kullanarak veya Escaping(kaçış karakteri) kullanarak. Ayrıca bunun için düzenli ifadelerden(regex) yararlanırız.

Düzenli ifadeleri daha iyi anlamak için önceden yayınladığımız yazımıza buradan ulaşabilirsiniz.

Biz bu yöntemleri kullanarak kullanıcının istenmeyen özel karakterleri kullanmasını engelleyebiliriz. Örnek bir White List kullanımına göz atalım.

regexPattern := regexp.MustCompile("[^a-zA-Z0-9 ]")
clean_username := regexPattern.ReplaceAllString(username, "") //kullanıcı adında harf ve rakam olmayan her şeyi silecektir

Bu şekilde kullanıcının sadece izin verdiğimiz karakterleri girmesini sağlamış oluruz.

Veri tabanına sorgu gönderirken onları temizlemiş olsanız bile ek önlem olarak parametreli sorgular kullanın. Hiçbir zaman değişkeni direkt olarak veri tabanında sorgulamayın. Bu SQL injection saldırısını önlemenin etkili bir yoludur.

SQL injection nedir buna da hemen göz atalım. SQL injection, hackerın uygulamadan veri tabanına gönderilen sorguyu manipüle etmesidir. Örneğin kullanıcı giriş sayfasında kullanıcı adı yerine bir SQL sorgusu eklerse ve biz bunu filtrelemeden ve parametreli olarak veri tabanında sorgulamadan direkt aşağıdaki verdiğim güvensiz örnekteki gibi sorgularsak hacker şifresini bilmeden kullanıcının hesabına giriş yapabilir.

//Güvensiz kullanım
db.QueryRow("SELECT password FROM users WHERE username = '"+username+"' ").Scan(&dbPassword)
//Güvenli kullanım
db.QueryRow("SELECT password FROM users WHERE username = ?", username).Scan(&dbPassword)

4. Least Privilege İlkesini Benimseyin(POLP)

Bu ilke kimseye gerekenden fazla yetki vermememiz gerektiğini söyler. Mesela bir veri tabanı yöneticisinin IT sistemlerine yönetici düzeyinde erişmesine gerek yoktur. Bu yüzden ona bu yetki verilmez. Bu şekilde veri tabanı yöneticisi bir siber saldırıya uğrasa bile saldırgan yalnızca yetkisi olan yerlere sızabilecektir, IT sistemlerine ulaşamayacaktır. Bu sebeple önemsenmesi gereken ve güvenlik açısından önemli bir ilkedir. Her istekte bizim izinleri doğrulamamız ve yetkisi olmayan kişilerin ulaşmasını engellememiz gerekmektedir. Ayrıca periyodik olarak bu izinleri gözden geçirmeli ve artık yetkiye ihtiyacı olmayan kişilerden bu yetkiyi almamız gerekir.

5. Session Yönetimi

Session ID'lerin HTTP üzerinden gönderilmesine izin vermeyin, HTTPS kullanın. Bunun için cookie'lerde secure ve HTTPONLY bayraklarını işaretlemelisiniz. Burada secure bayrağı cookie'nin sadece güvenli bağlantılar üzerinden taşınmasını sağlar. HTTPONLY bayrağı ise JavaScript'in cookie'ye erişmesini engeller ve XSS saldırısı ile çalınmasının önüne geçmiş olur.

Session ID nedir? Cookie nedir?

Bu bölümü daha iyi anlayabilmek için session ID ve cookie'nin ne olduğuna bir bakalım. Cookie'ler diğer bir adıyla çerezler, kullanıcıyla ilgili bilgileri kodlayan metin, karakter dizilerini içerir. Kısacası cookie'ler web sayfasının seni hatırlamasını sağlar.

Session ID diğer bir adıyla session token, web sayfasının kullanıcıya oturum açtığında atadığı benzersiz bir kimliktir. Oturum durumunu yönetmek için kullanılır. Cookie'ler daha az hassas bilgileri tutarken session ID daha kritik bilgileri içerir.

XSS nedir?

XSS, saldırganların kötü amaçlı JavaScript kodlarını, kurbanlarının web uygulamasına enjekte etmesini sağlar. Çoğunlukla yukarıda da bahsettiğim gibi cookie'lere erişmek için kullanılır.

Session ID'ler log dosyalarında, URL'lerde veya hata mesajlarında gösterilmemelidir. Ayrıca kullanıcı her oturum açtığında yeni bir session ID ataması yapılmalıdır. Bu şekilde session fixation saldırılarının da önüne geçilmiş olur.

Session fixation saldırısı şu şekilde gerçekleşir: Saldırgan yetkisi olan bir hesaba giriş yapar ve ona bir session ID atanır. Sonra bu ID'yi kullanarak kurbanına bir link göndermesi gerekir(session ID'sini URL'e yerleştirebilir, önlem alınmadıysa) . Eğer her yeni hesap açıldığında yeni session ID ataması yapmıyorsak kurbanın linke tıklayıp oturumunu açması durumunda saldırgan ile aynı session ID'ye sahip olacaktır. Yani saldırgan kurbanının hesabını ele geçirmiş olacaktır. Aşağıdaki görsel bu saldırıyı daha iyi anlamanıza yardımcı olacaktır.

Periyodik olarak kullanıcıların oturumlarını da sonlandırmanız önemlidir.

6. Clean Code

Clean code yani temiz kod yazımı güvenlik için çok önemlidir. Spagetti olmuş kodlar yazmak riskli kod bölgelerinin geç tespit edilmesini sağlar. Bu yüzden kodlarımızı olabildiğince temiz ve anlaşılır yazmalıyız.

Spagetti kod(spaghetti code) nedir?

Kodlarımızın karmaşık olması, okunabilirliğinin düşük olması demektir. Mesela kodlarımızın hepsini tek bir sayfada yazarsak biz buna spagetti olmuş kod diyebiliriz. Bu kodu düzenlemek de zordur, daha önce demiş olduğum gibi riskli bölgeleri tespit etmek de zordur.

7. Hata ve İstisna Yöntemini İzleyin

Hata mesajlarını kullanıcılara gösterirken genel mesajlar vermeye önem göstermeliyiz. Mesela veri tabanından gelen bir hata mesajını direkt olarak kullanıcıya göstermemeliyiz yoksa kullanıcının veri tabanımız hakkında bilgi almasını sağlamış oluruz ve potansiyel bir saldırgan bu bilgiyi kullanarak sisteme sızabilir. Ayrıca bu hata mesajları en az düzeyde ayrıntıyı göstermelidir. Örneğin kullanıcı giriş denemesi yaptığında bir yanlışlık varsa bu yanlış şifreden mi kaynaklanıyor yoksa kullanıcı adından mı bunun ayrıntısını göstermemelidir. Genel bir mesaj olmalıdır.

//Hatalı kullanım
if err != nil {
   dialog.Alert("Kullanıcı kaydedilirken bir hata oluştu: %v", err)//burada direkt hata mesajını ekrana basmak hatalı
 }
//Doğru kullanım
if err != nil {
   dialog.Alert("Kullanıcı kaydedilirken bir hata oluştu. Lütfen daha sonra tekrar deneyin.") //bu durumda hatanın nereden kaynaklandığını kodlama sırasında anlamak zorlaşır o yüzden hatalar log dosyasına kaydedilmeli
   savelog(err)
 }

Projenizi kodlarken "zaten şu anda sadece ben görüyorum sonra değiştiririm" diye düşünüp hata mesajlarını ekrana basmayın. Bir yerde bunu unutursanız saldırgana fırsat vermiş olursunuz. Onun yerine hata mesajlarını log dosyasına kaydedin ve kontrolleri buradan sağlayın.

8. Log Dosyanız Olsun

Log dosyanıza kimlik doğrulama bilgilerini, istenen kaynakla ilgili bilgiler ve kimin bu kaynağa erişmeye çalıştığını, hata mesajlarını, IP adreslerini mutlaka kaydetmelisiniz. Bu şekilde bir siber saldırı gerçekleştiği zaman IT ekibinin, bu saldırının nereden geldiğini ve hangi sistemleri etkilediğini tespit etmesine yardımcı olmuş olursunuz. Ayrıca bu işlemlerin ne zaman gerçekleştiğini takip edebilmek için zaman damgaları kullanmalısınız. Log dosyanıza kullanıcıdan gelen bir veriyi kaydedeceğiniz zaman temizlemeden kaydetmemelisiniz. Temizlemeden, olduğu gibi kaydederseniz ve bu log dosyasını IT ekibi bir web uygulamasında görüntülüyorsa, saldırganlar kötü amaçlı kod içeren bir mesaj göndererek kodları uzaktan çalıştırma imkanına sahip olabilir. Bu yüzden mottomuzu unutmuyoruz kullanıcıdan gelen girişe asla güvenme :). Bu girdiyi filtreledikten sonra log dosyasına kaydederek önlem alabiliriz. Bu filtreleme işleminde yine yukarıda bahsettiğimiz white list, black list, escaping gibi işlemlerden yararlanabilir ve bunun için de düzenli ifadeleri kullanabilirsiniz.

9. Kod Gizlemeyi (Obfuscation) Zorunlu Hale Getirin

Eğer bir web uygulaması geliştiriyorsak burada kod küçültme ve gizleme uygulamak, uygulamamızı tersine mühendislik saldırılarından koruyacaktır. Kod küçültme ile kodun okunması biraz zorlaşacaktır çünkü aradaki tüm boşluklar silinir ve bitişik yazılır ancak bu daha çok bellekte kapladığı alanı azaltmak için iyi bir yöntemdir, saldırganlara karşı büyük bir önlem sağlamaz. Kod gizleme ise kodu tamamen değiştirir ve okunmaz hale getirir bu yüzden kullanmak iyi bir uygulama olacaktır. Bunların örneklerine göz atalım.

//kodun normal hali
function fetchPosts() {
    fetch('/posts')
        .then(response => response.json())
        .then(posts => {
            const postList = document.getElementById('postList');
            posts.forEach(post => {
                const listItem = document.createElement('li');
                listItem.textContent = post.item; 
                postList.appendChild(listItem);
            });
        })
        .catch(error => console.error('Error fetching posts:', error));
}

fetchPosts();
//kodun küçültülmüş hali
function fetchPosts(){fetch("/posts").then(response=>response.json()).then(posts=>{const t=document.getElementById("postList");posts.forEach(post=>{const n=document.createElement("li");n.textContent=post.item,t.appendChild(n)})}).catch(error=>console.error("Error fetching posts:",error))}fetchPosts();
//bu ise kodun gizlenmiş hali. Kullanıcı bunu okumakta zorlansa da tarayıcı ilk halindeki gibi algılayacaktır.
const _0x3e40f3 = _0x32f5;
function _0x32f5(_0x46b078, _0x2e0c4c) {
    const _0x53bf65 = _0x53bf();
    return _0x32f5 = function(_0x32f50c, _0x5b7d22) {
        _0x32f50c = _0x32f50c - 0x14a;
        let _0x1cb894 = _0x53bf65[_0x32f50c];
        return _0x1cb894;
    }
    ,
    _0x32f5(_0x46b078, _0x2e0c4c);
}
function _0x53bf() {
    const _0x393540 = ['createElement', '235473ixWveR', 'getElementById', '661232rKzxfK', 'postList', 'innerHTML', 'json', 'appendChild', '210JAxGdw', '4423209DWcMhn', 'then', '176soIHLA', 'Error\x20fetching\x20posts:', 'catch', '2IRvDQR', '1595225pQaULC', '18SddECx', '66040DVrEBB', '647775GFGeUt', '939536DOIsSl', 'forEach'];
    _0x53bf = function() {
        return _0x393540;
    }
    ;
    return _0x53bf();
}
(function(_0x52d465, _0x23d2b4) {
    const _0x351c65 = _0x32f5
      , _0x29916c = _0x52d465();
    while (!![]) {
        try {
            const _0x244b04 = -parseInt(_0x351c65(0x15e)) / 0x1 * (-parseInt(_0x351c65(0x15d)) / 0x2) + -parseInt(_0x351c65(0x158)) / 0x3 + -parseInt(_0x351c65(0x15a)) / 0x4 * (parseInt(_0x351c65(0x14b)) / 0x5) + parseInt(_0x351c65(0x14a)) / 0x6 * (parseInt(_0x351c65(0x150)) / 0x7) + parseInt(_0x351c65(0x14d)) / 0x8 + -parseInt(_0x351c65(0x14c)) / 0x9 + parseInt(_0x351c65(0x157)) / 0xa * (parseInt(_0x351c65(0x152)) / 0xb);
            if (_0x244b04 === _0x23d2b4)
                break;
            else
                _0x29916c['push'](_0x29916c['shift']());
        } catch (_0x788cf) {
            _0x29916c['push'](_0x29916c['shift']());
        }
    }
}(_0x53bf, 0xe78b6),
fetch('/posts')[_0x3e40f3(0x159)](_0x5c1a74=>_0x5c1a74[_0x3e40f3(0x155)]())[_0x3e40f3(0x159)](_0x340f48=>{
    const _0x1b95ce = _0x3e40f3
      , _0x2fdab8 = document[_0x1b95ce(0x151)](_0x1b95ce(0x153));
    _0x340f48[_0x1b95ce(0x14e)](_0x468ba6=>{
        const _0x491396 = _0x1b95ce
          , _0x22ce4f = document[_0x491396(0x14f)]('li');
        _0x22ce4f[_0x491396(0x154)] = _0x468ba6['item'],
        _0x2fdab8[_0x491396(0x156)](_0x22ce4f);
    }
    );
}
)[_0x3e40f3(0x15c)](_0x6e9bf2=>console['error'](_0x3e40f3(0x15b), _0x6e9bf2)));
Kaynak Önerisi

Gizleme ve küçültme işlemini sadece JavaScript için değil diğer diller için de kullanabilirsiniz. Yukarıda size verdiğim kaynaklar JavaScript için geçerlidir.

10. Kodunuzun Kalitesini Kontrol Edin

Kodlarınızı yazarken Uluslararası kuruluşlar tarafından geliştirilen kodlama standartlarına uyup uymadığını kontrol edin. Bunun için CWE SANS 25 veya OWASP TOP 10'e bakabilir ve kodlarınızda burada belirtilmiş olan zafiyetlerin yer alıp almadığını kontrol edebilirsiniz. Ayrıca bunları kontrol etmek için statik kod analizi yapabilirsiniz. Statik kod analizi, bahsettiğim standartları kontrol ederek kodumuzu kritik bir zafiyet içeriyor mu diye tarar. Bu da gözümüzden kaçan noktaları tespit edip güvenlik önlemi alabilmemizi sağlar.

Statik Kod Analizi Tool Önerisi

  • SonarQube

  • PVS-Studio

  • Veracode

  • Embold

Yazımın en başında bahsettiğim gibi kodlamaya başlamadan tehdit modelleme yapılması, projenizi daha geliştirmeye başlamadan güvenlik açısından değerlendirebilmenizi sağlar. Bu sebeple mutlaka bu adım ile başlayın.


Kodlarınızı güvenli yazabilmeniz için elimden geldiğince önemli yerleri aktarmaya çalıştım. Buraya kadar okuduğunuz için teşekkür ediyorum. Güvenli kodlamalar diliyorum :)

Last updated