PythonYazılım

Python Veritabanı Programlama

Merhaba,

Bu blog içerisinde Python ile veritabanı programlama konusuna değineceğim. Günümüzde hemen hemen her alanda veritabanlarıyla çalışmaktayız. Yazılım projelerinin çoğunluğunda mutlaka veritabanı programlamaya zaman ayırılmaktadır. Hızlıca başlayalım.

Öncelikle bu blog içerisinde sqlite veritabanı ile çalışacağımı belirtmek istiyorum. SQLİTE büyük şirketler tarafından (Adobe, Apple, Google, Firefox..) aktif olarak kullanılan bir veritabanı sistemidir. Bu yüzden gerçekçi ve kullanmı rahat ayrıca python sürümleriyle uyumlu ve desteği olan bir veritabanı olduğu için sqlite tercih ettim. Google’da sqlite sample database şeklinde arama yaparak örnek veritabanı indirebilir ve alıştırmalar yapabilirsiniz. Aşağıdaki linki kullanarakta doğrudan indirebilirsiniz.

(https://cdn.sqlitetutorial.net/wp-content/uploads/2018/03/chinook.zip)

Veritabanını sürekli olarak görebilmek ve yaptıklarınızın sonuçlarını görsel olarak alabilmek için “SqliteBrowser” programını indirip kurabilirsiniz. Hem windows hemde linux sistemlerde sorunsuz çalışacaktır. (Ben Linuxta kullanıyorum herhangi bir sorun yaşamadım.)

Yeni Veritabanı Oluşturmak

Sqlite python programlama dilinde bütünleşik bir modül olarak gelmektedir. Python 2.5 itibariyle python’ın bir parçasıdır denilebilir. Bu yüzden her modül kullanımında olduğu gibi öncelikle modülü programımıza import etmemiz gerekiyor.

import sqlite3

Modülü import ettikten sonra connect() metodu ile halihazırda bulunan bir veritabanını kullanabilir yada yeni bir veritabanı oluşturabiliriz. Burada connect() veritabanına argüman olarak verilen dizinde veritabanı yoksa oraya argümanda berlitirlen veritabanı oluşturulur.

dbConnect = sqlite3.connect("yeniVeriTabani.db")

Bu şekilde çalıştığımız dizinde eğer yeniVeriTabanı.db adlı veritabanı yoksa bu isimde yeni bir veritabanı oluşturulacaktır.

Bu şekilde veritabanı sabit disk üzerinde oluşturulacak ve kalıcı bir veritabanı elde etmiş olacağız. Fakat bunun yanısıra bellek üzerinde (RAM) çalışmamız da mümkündür. Yani programımız kapandığında veritabanı kendisini imha edecektir! Bellek üzerinde geçici veritabanı oluşturmak için aşağıdaki kod kullanılılabilir.

dbConnect = sqlite3.conenct(':memory:')

Aynı şekilde Ram üzerinde değilde disk üzerinde de geçici (kendisini imha eden) veritabanları oluşturabiliriz. Bunun için ise aşağıdaki kodu kullanmalıyız.

dbConnect = sqlite3.conenct('')

Geçici olarak oluşturulan veritabanları veritabanı bağlantısı kesildiğinde (ilerleyen kısımlarda göreceğiz close() metodu) ortadan kalkacaktır.

İmleç Oluşturma

Connect() metodu ile yeni bir veritabanı oluşturduktan sonra yada mevcut bir veritabanına bağlanı sağladıktan sonra veritabanı üzerinde işlemler yapabilmek için önce bir imleç(cursor) oluşturmamız gerekir. Bunu için de kolayca cursor() metodundan yararlanacağız.

dbCursor = dbConnect.cursor()

Burada oluşturduğumuz dbCursor nesnesi sayesinde artık veritabanı üzerinde istediğimiz komutu, sorguyu rahatlıkla çalıştırabileceğiz. Bunun içinde execute() metodundan yararlanacağız. Aslına bakarsanız buradan sonrası tamamen SQL Sorgularıyla alakalı diyebiliriz. Tabikide ekleyeceğimiz noktalar olacaktır.

Tablo Oluşturma

Şimdiye kadar veritabanı ile bağlantı kurduk ve bir imleç oluşturduk şimdi ise execute() metodunu kullanarak bir tablo oluşturalım ve tablomuzda isim, soyisim, meslek, memleket sütunları yer alsın.

dbCursor.execute('CREATE TABLE musteriTablosu (isim,soyisim,meslek,memleket)')

Şuan da veritabanımzda musteriTablosu isimli bir tablo oluşturduk. Hemen kontrol edelim ve sqlitebrowser ile tablomuzu görelim.

Gördüğünüz gibi veritabanında tablomuz oluştu ve içerisinde ilgili sütunlar yer alıyor. Burada exucute() metoduna gördüğünüz gibi sql komutumuzu String olarak verdik. Buda demek oluyor ki pythondaki string ifadeleri için geçerli olan kurallar ve kullanımlar aynı şekilde buradada geçerli olacaktır. Aynı zamanda biz komutu (CREATE TABLE…… komutunu) bir değişkene atayarak execute() metoduna bu değişkenide verebilirdik. Sonuç değişmeyecektir. İşin güzel tarafı burada string şablon formatlamada yapabiliriz.

Şimdi şöyle bir durumla karşı karşıya kalırsak, veritabanında bir tablo oluşturmak istiyoruz fakat komutunu/kodumuzu çalıştırdığımızda “table ‘musteriTablosu’ already exists” hatasını alıyoruz. Bu durumda anlıyoruz ki zaten veritabanında oluşturmak istediğimiz tablo mevcut. Fakat programımız burada hata alıyor ve sonlanıyor. Bunun önüne geçmek için tabikide try except bloklarını kullanabiliriz. Fakat bu soruna çözüm olarak SQL Komutu içerisinde basit bir ifade kullanabiliriz. Bahsettiğim ifade şudur;

dbCursor.execute('CREATE TABLE IF NOT EXISTS musteriTablosu (isim,soyisim,...)')

Bu kodu tekrar tekrar çalıştırsak dahi programımız hata vermeyecek ve çalışmasına devam edecektir. Yani veritabanında musteriTablosu adlı bir tablo yoksa bu tabloyu ve sütunları oluşturacaktır fakat bu tablo zaten mevcutsa o zaman hiçbir şey yapmayak ve yoluna devam edecektir. Bu tablo oluşturma yöntemine Şartlı Tablo Oluşturma adı verilmektedir.

Tabloya Veri Ekleme (INSERT)

Şimdi tablomuza yeni veriler ekleme noktasına geldik. Burada yine execute metodundan yararlanacağız. Bildiğimiz üzere SQL komutları arasında yer alan INSERT Komutu tabloya yeni verileri eklememizi sağlar. Aşağıda tüm adımları gerçekleştirdiğim kod blogunu görmektesiniz.

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sqlite3

dbConnect = sqlite3.connect("yeniVeriTabani.db")    #bağlantı kur.
dbCursor = dbConnect.cursor()                       #imleç oluştur.

tableCreate = 'CREATE TABLE IF NOT EXISTS musteriTablosu (isim,soyisim,meslek,memleket)'
insertValuesInTable = """INSERT INTO musteriTablosu VALUES ('Hasan','Baskın','Adli Bilişim Mühendisi','Antalya') """

dbCursor.execute(tableCreate)           #tablo oluştur.
dbCursor.execute(insertValuesInTable)   #tabloya değerleri ekle.

dbConnect.close()   #bağlantıyı kes

Burada INSERT komutunun yer aldığı kod parçasını çalıştırdığımız zaman tabloya bilgilerin eklenmesi lazım fakat sqlitebrowser ile kontrol edersek verilerin veritabanına eklenmemiş olduğunu göreceğiz.Bunun sebei bir hata yapmamız değil kodumuzda bir eksiklik olmasıdır. YAni biz veri tabanına verileri ekledik fakat henüz işlemedik! Şimdi commit metodundan yararlanarak verilerimizi işleyelim ve tekrar kontrol edelim. Yani yeni kodumuz şu şekilde olacaktır.

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sqlite3

dbConnect = sqlite3.connect("yeniVeriTabani.db")    #bağlantı kur.
dbCursor = dbConnect.cursor()                       #imleç oluştur.

tableCreate = """CREATE TABLE IF NOT EXISTS musteriTablosu (isim,soyisim,meslek,memleket)"""
insertValuesInTable = """INSERT INTO musteriTablosu VALUES ("Hasan","Baskın","Adli Bilişim Mühendisi","Antalya") """

dbCursor.execute(tableCreate)           #tablo oluştur.
dbCursor.execute(insertValuesInTable)   #tabloya değerleri ekle.
dbConnect.commit()                       #verilerin veritabanına işlenmesi.

dbConnect.close()   #bağlantıyı kes.

Kodu çalıştırdıktan sonra veritabanımızdaki tablo içeriği aşağıdaki gibi olacaktır.

Buradada gördüğünüz gibi veritabanını kapatmak için close() metodunu kullanıyoruz.

Buraya kadar olan kısımda temel kullanımları kodladık. Aslında şuan için veritabanı üzerinde tüm işlemleri yapabilecek noktaya geldik. Buradan sonrası üzerine koymak ve geliştirmek diyebilirim. Şimdiyse uygulamalar ve örnekler yaparak farklı kullanım türlerini kodlayalım.

Gerçek Hayattan Bir Kodlama Yapalım

Bu noktaya kadar sorgularımızıı program içerisinde kendimiz yazdık ve execute ettik. Fakat gerçek hayatta veritabanına girilecek olan veriler harici kaynaklardan gelecektir yada kullanıcı tarafından arayüz vasıtasıyla tanımlanacaktır. Bu noktada hem verilerimizi veritabanına eksiksiz ve doğru bir şekilde ekleyip işlememiz gerekirken aynı zaman veritabanının ve dolayısıyla verilerin güvenliğini sağlamakta bizim işimiz olacaktır.

Şimdi datas adlı bir değişken içerisindeki verileri döngü oluşturarak veritabanına ekleyelim.

#!/usr/bin/python3
import sqlite3

dbConnect = sqlite3.connect("yeniVeriTabani.db")  
dbCursor = dbConnect.cursor()    

datas = [('Altar','Turkboy','Asker','Sogut'),('Adem','Kara','Doktor','Rize'),('irem','Burgaz','ogretmen','Antalya')]

dbCursor.execute("""CREATE TABLE IF NOT EXISTS musteriTablosu (isim,soyisim,meslek,memleket)""")

for data in datas:
    dbCursor.execute("""INSERT INTO musteriTablosu VALUES (?,?,?,?)""", data)

dbConnect.commit()
dbConnect.close()

Kodumuzu yukarıdaki gibi yazdıktan sonra çalıştırdık ve sqlitebrowser ile veritabanımızı kontrol ettik. Tablomuzun yeni hali şu şekilde olacaktır.

Gördüğünüz gibi musteriTablosu isimli tablomuza verilerimiz eklendi. Şuana kadar veri ekleme işlemlerini yaptık ve tablomuzda toplamda 4 adet kayıt mevcut. Şimdi mevcut kayıtlar içerisinden SELECT sorgusu ile bazı verileri okuyalım.

Verileri Seçmek ve Almak – Fetchall()

Bu aşamada tablodaki verilerimizi SELECT ifadesini kullanarak seçeceğiz ve fetchall() metodu yardımıyla bu verileri alacağız. Burada standart SQL şart ifadeleri kullanılarak (where ifadesi, LIKE ifadesi..) seçim kombinasyonları değiştirilebilir. Tablodaki tüm içeriği alabilek için kodlamamızı şu şekilde yapmalıyız.

#!/usr/bin/python3
import sqlite3

dbConnect = sqlite3.connect("yeniVeriTabani.db")  
dbCursor = dbConnect.cursor()  

dbCursor.execute("""SELECT * FROM musteriTablosu""")
datas = dbCursor.fetchall()
print(datas)

dbConnect.close()

Kodumuzu terminalde çalıştırdığımız zaman çıktısı şu şekilde olacaktır. Sizler için doğrudan çıktının ekran görüntüsünü buraya ekliyorum.

Gördüğünüz gibi tablo içeriğini bir liste olarak ekrana yazdırdık. Burada verileri almak için fetchall metodunu kullandık. Yani tüm verileri al demiş olduk. Bunun yerine aşağıdaki metotlar da kullanılabilir.

fetchone metodu: Sadece bir tane veri alır.

fetchmany metodu: Kaç tane veri alınması gerektiğini argüman olarak veririz.

VERİTABANI GÜVENLİĞİ

Eveeet şimdi geldik işin en önemli ve zevkli olan kısmına yani güvenlik kısmına…

Bu noktada neredeyse herkesin bildiği SQL INJECTION zaafiyeti aklımıza gelmiş olmalıdır. Gelmediyse de artık sizinde aklınızda yer edinecektir. Yazılım projelerinde veritabanı ile çalışılması esnasında mutlaka veritabanı güvenliği ve yazılım güvenliği yazılımcı tarafından sağlanmak zorundadır. Eğer böyle bir proje üzerine çalışıyorsak ve veritabanında özel, gizli veya kişisel veriler tutuyorsak mutlaka ama mutlaka güvenliği sağlamak zorundayız. Peki sql için sql injection saldırısına karşı nasıl bir tedbir almamız gerekiyor. Şimdi bu tedbirleri kod üzerinde uygulamalı olarak görelim.

Şimdi terminal üzerinden kullanıcıdan kullanıcı adı ve parola bilgilerini alacağız ve sonrasında veritabanından bu bilgileri konrol ederek kullanıcıya bilgi vereceğiz.

#!/usr/bin/python3

import sqlite3

dbConnect = sqlite3.connect('userAuthData.db')  #Veritabanina baglan.
dbCursor = dbConnect.cursor()                   #imlec olustur.

dbCursor.execute("""CREATE TABLE IF NOT EXISTS users (username,password)""")    #Kullanici adi ve parola tutan bir tablo ekle.

datas = [('admin','123456'),('hasan','2021'),('haydar','hay123dar')]            #Kullanici verileri olustur.

for data in datas:
    dbCursor.execute("""INSERT INTO users VALUES %s"""%(data,))                 #Kullanici verileri tek ogeli bir demet ile tabloya yerlestir.

dbConnect.commit()      #Verileri veritabanina isle.

userName = raw_input("Username: ")  #Kullanici adi giris
passWord = raw_input("Password: ")  #Parola giris

dbCursor.execute("""SELECT * FROM users WHERE username = '%s' AND password = '%s'"""%(userName,passWord))   #Kullanici adi ve Parolayi Veritabanindan dogrula

dataFromDb = dbCursor.fetchone()    #Bir tane veriyi al.


if dataFromDb:
    print("Login Success, welcome {}".format(dataFromDb[0]))
else:
    print("Access Denied !")

Gördüğünüz gibi kodlarımız bu şekilde. Şimdi çıktısını görelim ve deneyelim.

Çıktıda gördüğünüz gibi sorunsuz bir şekilde doğrulama yapılıyor ve doğru bilgiler girilince karşılama yapılarak kullanıcı sisteme başarıyla login olabiliyor. Fakat burada büyük bir hata ve zaafiyet var! Şimdi hiçbir kullanıcı adı ve parola bilgisi olmadan giriş yapmayı deneyelim. Nasıl mı?

İşte böyle.. username ve password alanlarına şu sql injection ifadesini yazalım ve deneyelim. (Mutlaka denemelisiniz !)

blabla' OR '7' = '7

Evet bu ifadeyi her iki alanada yazın ve Enter’a basın. Göreceksiniz ki sistem sizi içeriye alacak. Hemen sizin için ekran görüntüsünü de eklemek istiyorum.

Gördüğünüz gibi sistem bizi içeriye akdı hemde admin kullanıcısı olarak !!!

Buraya çok dikkat etmelisiniz. Sistem login ekranını bypass ettik ve dahası sisteme en yetkili kullanıcı olarak yani admin olarak giriş yaptık. Şuan kendi gözlerinizle veritabanı güvenliğinin ne kadar önemli olduğunu görmüş oldunuz.

Burada sadece kullanıcı adı kısmına aşağıdaki ifadeyi yazmanızda yeterl olacaktır.

blabla' OR '7' = '7' --

Peki burada ne oldu?

Burada sona eklediğimiz (iki tane çizgi) sistemin sql sorgusu içerisindeki parola kısmını yorum satırına çevirdi. Biliyorsunuz ki Python dilinde yorum ve açıklama eklemek için # karakterini kullanıyoruz. SQL için açıklama satırı yorum satırı işareti olarakta karakterlerini (iki çizgi) kullanıyoruz. Yani kolayca parola kısmını by-pass ettik.

! Zaafiyeti İnceleyelim !

Yukarıdaki sql injection ifadesini username ve password kısmına yazıp Enter’a bastığımızda bakın arka planda yazmış olduğumuz sorgu nasıl bir hal alıyor.

im.execute("""SELECT * FROM kullanicilar WHERE
username = 'blabla' OR '7' = '7' AND password = 'blabla' OR '7' = '7'""")

Biliyoruzki OR ifadesinin sadece bir tarafının sağlanmasıyla bool değeri True olacaktır. Fakat AND ifadesinni her iki tarafına doğru olduğu zaman ancak True ifadesi döndürülecektir. Burada blabla’ OR ‘7=’7 ifadesi sorgu içerine yerleşerek 7’nin 7’ye eşit olması durumda True değer döndürmekte ve kullanıcı doğrulaması sağlanmaktadır.

Başlangıçta ve sonda kesme işareti eklemiyoruz çünkü zaten sorgu içerisinde bu işaretler yer alıyor. Bir nevi araya kaynak yapmış oluyoruz..

Buna Sebep Olan Yer Neresi ?

Hatırlarsanız kod içerisinde kullanıcı adı ve parola bilgisini aldıktan sonra bunları sql sorgusuna %s kullanarak yerleştirmiştik. Buraya tedbir olarak %s yerine sadece ? kullanmak büyük ölçüde bu saldırının önüne geçecektir fakat tam manasyıla koruyacaktır diyemeyiz.!

Bunun haricinde SQL sorgularını execute metoduna vermeden yani işleme koymadan önce belirli filtrelerden geçirmeliyiz. Bu noktada kullanıcı adı ve parola belirleme politikaları kapsamında regexler yazılabilir, yalnızca alfanumerik karakterler kullanılabilir. Bu sayede kullanıcı adı ve parola alanlarına yazılabilecek karakterler sınırlandırılabilir.

!! DİKKAT !!

Yukarıda giriş bilgilerini by-pass ettiğimizde sisteme admin olarak giriş yaptığımızı gördük. Peki bunun sebebi nedir ?

Sizinde gördüğünüz gibi veritabanı tablosunda ilk sırada yer alan kullanıcı adı “admin” ve parola bilgisidir. Yani bir saldırgan sql injection ile giriş ekranını by-pass ederse tablonun ilk sırasında yer alan kullanıcıyı ele geçirmiş olacaktır. Bu nedenle ek bir güvenlik tedbiri olarak asla tablonun ilk kaydına admin kaydını eklememeliyiz. Ayrıca ilk sırada sisteme zarara verebilecek durumdaki bir kullanıcı bilgisi yer almamalıdır. İsterseniz bu noktada tüm faaliyetleri sınırlandrılmış bir kullanıcı ekleyebilirsiniz.

ÖZET

Bu blog içerisinde python programlama dili ile birlikte sqlite veritabanını kullarak veritabanı programlama konusuna değindik. Python içerisinde ki sqlite3 modlünün kullanımlarından bahsettik. Ve sonrasın bazı veritabanı işlemlerini simüle ederek en sonda çok önemli bir konu olan GÜVENLİK konusuna değindik. Bu noktada sql injection saldırılarına karşı alınabilecek temel tedbirleride anlatmış olduk.

Bu noktaya kadar eksiksiz olarak her şeyi anladıysanız artık projelerinizde güvenli bir şekilde veritabanlaını kullanabilirsiniz. Lakin şunu bilmelisiniz ki hiçbir sistem güvenli değildir. Yani alınan tedbirler %100 güvenlik sağlar diye bir şey söyleyemeyiz.

Umarım sizler için faydalı olmuştur. Blog içerisinde yazdığımız kodların tam çalışır hallerini ve database dosyalarını Github’a sizler için yükleyeceğim. Aşağıdaki github butonunu kullanarak kodlara erişebilir ve kendi makinenizde çalıştırarak anlattıklarımı uygulayabilirsiniz.

Okuduğunuz için teşekkür ederim.

Sağlıklı ve huzurlu günler dilerim.

Sağlıcakla kalın…

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir