Sıradan bir legacy code hikayesi

f.
f.
Jul 26, 2017 · 5 min read

Legacy code her zaman kötüdür, değil mi? Eski programcının kodlarına bakar, bir çok hata görür, “böyle mi yapılır bu” diye söyleniriz. Bir iki günlük -ya da haftalık, kodun içine girme süreniz bağlı- Dunning Kruger sendromu sonrası mimariyi ve içinde bulunduğu kısıtları gördüğünüzde birkaç refactoring dışında bir şey ekleyemez olduğunuzu görürsünüz. Proje tıkanır, “bu adam olmaz, yeniden yazmak gerek” dersiniz. İşte o an aslında fark edilmesi gereken şey sizin kodunuzun da -eğer olacaksa, yaşam döngüsünün sonunda değilse- bir sonraki programcı tarafında aynı tepkiyle karşılaşacağı olmalı. Çünkü sizin kodunuz da derlediğiniz andan itibaren artık legacy code’dur. Ve kabul edelim, hepimiz kötü kod yazıyoruz.

Her konu hakkında bir XKCD vardır.


Bir kurum (her ne kadar kurum içi bir proje olsa da anlaşılırlık açısından bundan sonra “müşteri” olarak bahsedeceğim) için “acil” web sayfası istenmiş. Tabii ki isteyenler web sayfası dedikleri şeyin bir CMS ve bir arayüzdenden oluşan iki ayrı çalışma içereceğini bilmedikleri gibi, işi takip etmek üzere görevlendirilen “uzman” da bu konudan bihaber. Haliyle işi ehil olmayan birine yaptırmışlar ve ortaya işleyen ama hatalarla dolu bir ürün çıkmış. Ben bu işe sonradan “bir bakar mısın” ricasıyla dahil oldum. Zira sözleşmedeki ürün teslim süresi dolalı iki ay olmuş ancak hem geliştirici hem de müşteri tarafından kaynaklanan sebepler nedeniyle bu gecikmeye kimse ses etmemiş. Ürün alpha test aşamasındayken production’a çıkmış, hatalı da olsa yayında kalmış. Bana da ürüne bir göz atmak ve süreci sonlandırmak adına “kabul edilebilir” onayı vermek düşmüştü. İşin ilginç yanı bu kabul edilemez dediğimde artık çok geç olduğunu, mevcut olanın idare edecek kadar düzeltilmesi için bir şeyler yapmam söylendi. Doğrusu bu açıklamaya direndim ve 4–5 sayfalık bir To-Do list hazırlayıp geliştiriciye yolladım. Zira ürün <herhangi bir programlama dili> kursunda ders alan 2 aylık öğrencinin geliştirebileceği seviyedeydi ve kritik seviyede hatalı tasarım kararı verilmişti. Bir rewrite gerekliydi ama mevcut olanı derleyip toplamak şu an ön plandaydı. Listeye hem müşteri hem de geliştirici tarafından tepkiler geldi. Geliştirici en başından verilen bazı mimari kararların değişmesinin neredeyse yeniden yazmaya eşdeğer olduğunu ve bu kararların bizzat müşteriyle görüşülerek verilmesi nedeniyle isteklerimin sözleşmeye uygun olmadığını söyledi -ki haklıydı. Müşteri ise verdiğim listedekilerin gereksiz olduğunu, zaman içinde kendilerinin de bu istekleri birilerine “hallettirebileceklerini” ve “meseleyle boğuşmamamı” söyledi. O kadar para döküp sonuçta kötü olduğunu belirttiğim bir ürünü eksikleriyle kabul edip kalanını kendi personeline yaptırması aklıma yatmasa da “danışman” rolüyle yapabileceğimin bu kadar olduğunu kabul ettim.

Yukarıdaki şikayet metni görünümlü spesifik olay, çok basit bir şeyi ifade etmek içindi: Legacy code genellikle kötüdür. Ama kötü olmasının tek sebebi geliştirici değildir. Müşterinin ve diğer karar vericilerin kararları da kötü kodun sebebidir ve bu mirasın parçasıdır.

Asıl hikaye sonra başladı. Bir gün White Hat bir hacker, siteyi bir zafiyet tarayıcısı ile kontrol etmiş ve açıkları olduğunu tespit etmiş. SQL injection açığı sayesinde de veri tabanına ulaşıp SQL service’i kapatmış, ardından kuruma e-posta ile ulaşmış. Bir cumartesi günü panik halinde toplanmışlar. Ardından beni çağırdılar. Yaklaşık 3 aydır söylediğim ve kendi personeline “hallettireceklerini” iddia ettikleri düzeltmeleri yapmamışlar ve pimi çekilmiş bir bomba olarak tesadüfen bu kadar süre hayatta kalan web sayfası doğal olarak patlamış. Bu aşamadan sonra mevcut kodu düzeltmeyi kendim için bir görev addedip geliştirme yaptım. Zira üzerinde çalışmak için güzel bir vaka idi.

Not: Yazıdaki konu sırası bir önem ya da öncelik sırası değildir. Olayla ilgili olarak yapılan işlemlerin kronolojik sıralamasına dayanmaktadır.

İlk inceleme

Geliştirici .Net kullanmış, çünkü müşteri öyle istemiş. Neden, hala bilmiyorum. Proje yapısında App_Start, Controllers, Models, Views klasörlerini görünce “güzel, en azından MVC kullanmış” dedim. Ama klasörlerin içinin boş olduğunu görünce hayal kırıklığı ile karışık bir gülme hissi oluştu. Zira belli ki VS 2015 ile yeni bir MVC projesi açmış ancak sayfaları bildiği çözüm olan WebForms ile yazmış. Haliyle boilerplate olarak gelen pek çok özellik atıl durumda kalmış, üstüne kalabalık yaparak kafa karıştırmış. Ya WebForms ya MVC yapmasını, böyle işlevsiz bir hibridin işe yaramayacağını söylemiştim ancak düzeltme yapılmamış. Dolayısıyla code behind içine yığılmış SQL komutları üzerine inşa edilmiş bir CMS ile baş başa kaldığımız ortaya çıktı.

SQL Injection 101

Parametreli SQL ifadeleri

ASP.NET üzerinde SQL injection’ı temel olarak önlemek basit: Parametreli SQL komutları kullanmak.

JSP’de olduğu gibi burada da temel çözüm bu. Kullanıcıdan gelen girdileri string concatenation ile eklemek SQL injection’ı rahatça kabul etmek demek. Aşağıdaki kodu değiştireceğiz.

var command = new SqlCommand(
“INSERT INTO Events(EventDate, EventContent, EventLocation)
VALUES(“ + eventDate + “,” + eventContent + “,” + eventLocation “)”, connection);
command.ExecuteNonQuery();

String concatenation ile dahil edilmiş C# değişkenlerini, parametre ile ekleyerek kodu yeniden yazacağız.

var sqlCommand =new SqlCommand(
“INSERT INTO Events(EventDate, EventContent, EventLocation)
VALUES(@eventDate, @eventContent, @eventLocation)”, conn);
command.Parameters.Add(
new SqlParameter(“eventDate”, eventDate));
command.Parameters.Add(
new SqlParameter(“eventContent”, eventContent));
command.Parameters.Add(
new SqlParameter(“eventLocation”, eventLocation));
command.ExecuteNonQuery();

SqlParameter sınıfı da SqlCommand gibi .Net kitaplığında System.Data.SqlClient namespace’inde yer alan bir sınıf. Bu sınıfın constructor metodu görüldüğü üzere iki parametre alıyor. İlki olan string tipindeki parametre, SQL ifadesinde “@” ile başlayan SQL parametresini ifade ediyor. Object tipindeki ikinci parametre de ilgili SQL parametresine gönderilecek değeri ifade ediyor. Burada dikkat etmeniz gereken bir nokta var. SQL parametresinin türü integer, varchar, double olabilir. Ancak siz bunu Visual Studio üzerinden anlayamazsınız. Static typing’e alışık .Net geliştiricisi için compile time’da belli olmayan tip hatası garip gelebilir, normaldir. Intellisense yazdığınız bir string üzerinden (SQL komutunuz) tabloya ulaşıp ilgili sütunun veri tipini alabilecek kabiliyette değil. Bunu yapmak için bir ORM kurmanız gerekiyor. Siz ikinci parametreye doğru veri tipinde değer girmek zorundasınız. Varchar yerine integer, int/tinyint alan bir sütuna string değer gönderirseniz SqlException hatası alırsınız. Bu yüzden mutlaka kontrol etmelisiniz.

Bir ORM kullanırsanız, kendisi bu konuda tedbirlerini almış olacağı için elinizi rahatlatabilir. Nitekim arka planda yaptığı da yukarıdakilerden ibaret olacaktır.

Girdi Denetimi

WebForms sayfalarında kullanıcının girdi yollayabileceği iki yer var. Biri form girdileri, yani metin kutuları, diğeri adres satırından gelecek sorgular.

var textBoxInput = TextBox1.Text; // Örnek metin kutusu girdisi
var queryFromAddressBar = Request.QueryString["q"]; // Örnek sorgu

Yukarıdaki iki potansiyel kaynağı bir Helper Class’ı içinden IsSqlInjection adında bir metot ile kontrolden geçirmek istiyoruz. Metot sözleşmesi aşağıdaki gibi olacak.

public class SqlInjectionHelper
{
public static bool IsSqlInjection(string[] inputs)
{
// Regex ve sık kullanılan SQL injection ifadelerini kontrol eden işlemler burada gerçekleştirilmeli
}
}

Bu şekilde aldığımız girdileri şöyle bir if-else ifadesi ile denetleyebiliriz.

Response.Redirect(
SqlInjectionHelper.IsSqlInjection(new[] { queryFromAddressBar })
? "Http404.aspx"
: $"Search.aspx?q={queryFromAddressBar}" );

Gördüğünüz üzere potansiyel bir SQL Injection durumunda Http404 sayfamıza yönlendirdik. Bunun yanında log mekanizmamızı tetikleyen başka işlemler ekleyerek daha kontrollü bir savunma geliştirebiliriz.

Tabii ki Query String böyle açıkken güvenli değil. Bunun için query string encryption şeklinde bir arama yaparsanız pek çok başarılı çözüme ulaşabilirsiniz. Şu Code Project makalesi güzel bir fikir verecektir.

Visual Studio Magazine bu konuda yeni bir yazı paylaşmış: https://visualstudiomagazine.com/articles/2017/07/01/parameterized-queries.aspx

Ancak genelde Utility, Core, Infrastructure gibi namespace’ler altında yer alan bu biçimdeki statik Helper sınıfları bir code smell’dir. Bu nedenle WebForms için HttpModule, MVC için Action Filter, ASP.NET Core için Middleware kullanmak en doğru yoldur.

Hesap Yönetimi

Bir sonraki yazıda CMS’in oturum açma ekranında parolayı clear text olarak gönderen arayüze el atacağım. Öncesinde de hesap yönetiminin teorisinden bahsedeceğim.

f.

Written by

f.

Zafer Balkan - SysAdmin in the day, Developer at night

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade