Selamün Aleyküm
eğer input güvenliği sağlanmazsa kullanıcı verdiğimiz input alanına zararlı kodlar girip veritabanımızdan bilgiler çekmeye çalışabilir bu sadece veritabanı ilede alakalı değil xss açığı gibi açıklarda oluşabilir
sitemizde bir register sayfası var kişi oradan kayıt olabilir ve login sayfasındanda kullanıcı adını girerek kendi şifresini öğrenebilir
sitemizin input alanında ve zamanlama konusunda mantıksal zafiyetleri var bunları kapatmaya çalışcaz
öncelikle güvensiz input alanı koduna bakalım
Python:
from flask import Flask,request
import sqlite3
db_name = 'database.db'
app = Flask(__name__)
@app.route("/", methods=['GET', 'POST'])
def ana_sayfa():
login_mesaj = ""
if request.method=='POST':
username = request.form['username']
if not username:
login_mesaj = "kullanıcı mevcut değil"
else:
baglan = sqlite3.connect(db_name)
cursor = baglan.cursor()
kullanici_bilgisi = f"SELECT * FROM users WHERE name = '{username}';"
cursor.execute(kullanici_bilgisi)
rows = cursor.fetchall()
if not rows:
login_mesaj = "kullanıcı mevcut değil"
else:
rows = rows[0]
login_mesaj = rows
cursor.close()
baglan.close()
return '''
<h1 style="text-align: center;">şifrenizi öğrenin</h1>
<br>
<style>
form{
text-align: center;
}
</style>
<form id="gönder" action="/" method="POST">
<input type="text" name="username">
<input type="submit" value="gönder">
</form>
'''+'''
<h1 style="text-align: center;">{}</h1>
'''.format(login_mesaj)
if __name__ == "__main__":
app.run(debug=True)
bu kodda input alanına girilen değerler kontrol edilmeden sql sorgusuna dahil edilmiş site üzerinde bir test yapalım
input alanına normal bir isim giriyoruz
herhangi bir sorun yok şimdide input alanına tırnak işareti koyduğumuzda ne oluyor ona bakalım
gördüğünüz gibi sql syntax hatası aldık artık kullanıcı bu açık sayesinde veritabanı ile etkileşim kurabilir
1) input güvenliği
peki input güvenliğini nasıl sağlıycaz
bu siteye ve girilmesi gereken değerlere göre değişir ama örnek bir site için şunları yapabiliriz :
1) input alanına boşluklu karakter girilmişmi girilmemişmi bunu kontrol edicez
2) ardından özel karakter kullanılmışmı bunu kontrol edicez
3) sql payloadlarını verilen input değeri ile karşılaştıracaz
4) xss payloadlarını verilen input değeri ile karşılaştıracaz
eğer verilen input'da bu değerlerden biri varsa herhangi bir sql sorgusu yapmıycaz kullanıcıya sanki sql sorgusu yapmış gibi kullanıcı mevcut değil cevabını döndürücez
mesela 1. fonksiyonda zararlı girdi tespit edildi diyelim o zaman kodun gereksiz çalışmaması için 2 - 3 ve 4. fonksiyonları çalıştırmıycaz
şu ana kadar iyi bir şekilde input güvenliğini sağladık
ama bu seferde kötü niyetli kullanıcı bizim ne tür girdileri yasakladığımızı öğrenebilir peki bu nasıl olur ?
2) zamanlama zafiyeti
kısa bir videoda açıkladım :
Video
videodada izlediğiniz gibi deneme yanılma yöntemi ile kullanıcı hangi girdileri yasaklayıp yasaklamadığımızı kolay bir şekilde mantık yürüterek anlayabilir tabi anlasada biz güvenliği sağladıktan sonra ne kadar etkili olur orasıda ayrı konu ama biz yinede önlemimizi alalım
kullanıcının ne tür girdileri engellediğimizi anlamaması için ne yapıcaz ?
öncelikle kullanıcı zararsız bir girdi girerse kaç ms (mili saniye) sonrasında cevap dönüyor buna bakıcaz benim kodumda normal bir girdi sonrasında 4 ms gibi bir zaman sonunda cevap dönüyor bu durumda ben bütün cevap sürelerini 4ms'ye tamamlamaya çalışcam
örnek olarak zararlı bir girdi yapıldı ve kodumuz 1ms lik bir zamanda zararlı girdiyi tespit etti o zaman kodumuza 3 ms'lik bir bekleme süresi ekliycez ve kullanıcıya cevap gitme süresini değiştiricez bu durumda kullanıcının kodumuzun nasıl bir mantık ile çalıştığını anlaması daha zor olacak bekleme süreleri siteye ve işleyişine göre değiştirilebilir.
şunu diyebilirsiniz neden süre artırmak için bu kadar uğraştın girdiyi taramak için if else bloklarını kullanma hepsi sıra sıra çalışsın en sonunda zaten kullanıcı zararlı girdi yapsada yapmasada süre aynı olacaktır ama bu seferde kodlar gereksiz çalışmış olur ve çok fazla kullanıcınızın olduğunu düşünürseniz gereksiz kodları her kullanıcı için çalıştırmak mantıklı olmaz bu durumda bekleme süresi eklemek en mantıklısı (yanlış düşünüyorsam nedeni ile açıklarsanız sevinirim)
kodlara geçelim
kodu kendiniz çalışmak isterseniz Github dan indirebilirsiniz
burası python'da web sitesini çalıştırmak için ana sayfa burayı geçip direk güvenli kodlar kısmına bakabilirsiniz
from birleştir import second yerleri ne derseniz oralar python ile yazdıgımız web sitelerini içe aktarmak için
( Flask kütüphanesi ile ilgili Flask bilmiyosanız oraları boşverin önemli bişey değil siz genel mantığa odaklanın )
Python:
from flask import Flask
from birleştir import second
from birleştir import second1
app = Flask(__name__)
app.register_blueprint(second)
app.register_blueprint(second1)
@app.route("/")
def ana_sayfa():
return """
<h1 style="text-align: center;">ana sayfa</h1>
<br>
<style>
form{
text-align: center;
}
</style>
<form action="/register" method="get">
<input type="submit" value="kayıt ol">
</form>
<br>
<form action="/login" method="get">
<input type="submit" value="şifreni öğren">
</form>
"""
if __name__ == "__main__":
app.run(debug=True)
Güvenli Kodlar
öncelikle bir tablo oluşturalım burası sadece bir defa çalıştırılacak
Python:
import sqlite3
baglan = sqlite3.connect("users.db")
cursor = baglan.cursor()
tablo_oluştur = "CREATE TABLE IF NOT EXISTS users(name,password);"
cursor.execute(tablo_oluştur)
cursor.close()
baglan.close()
burasıda register ve login sayfasının bulunduğu python kodu
Python:
from flask import Flask,request,Blueprint
import time
import random
import sqlite3
db_name = 'users.db' # database dosyanın adı bu dosyaya kullanıcıları ekliycez
# githubdan aldığım payloadlar
xss_payload = open(r"xss.txt",'r',encoding='utf8').read().splitlines()
sql_payload = open(r"sql.txt",'r',encoding='utf8').read().splitlines()
# ===========================
# paylodları direk aldığımız gibi kullanmak pek içime sinmedi paylodları direk kullanırsak kullandığımız payloadlar tespit edilebilir o yüzden indexleri karıştırıcaz
xss_index = list()
[xss_index.append(i) for i in range(len(xss_payload))]
random.shuffle(xss_index) # indexlerin yerlerini karıştırıyoruz
# ===========================
sql_index = list()
[sql_index.append(i) for i in range(len(sql_payload))]
random.shuffle(sql_index)
# ===========================
karakterler = "! \' ^ # + $ % & / { ( [ } ) ] ? * \\ , ` : | ´ < > \" . : - _".split() # engellediğimiz karakterler
# any fonksiyonu sayesinde döngü icinde bir tane bile True değeri varsa sonuç olarak bize True değerini döndürür
# bu yazım tarzı zor geldiyse chatgpt'den normal halini alabilirsiniz
özel_karakter_kontrol = lambda girilen_deger: any(i for i in karakterler if i in girilen_deger) # özel karakterlerden biri girilen değerin içindeyse
xss_kontrol = lambda girilen_deger: any(xss_payload[i] for i in xss_index if xss_payload[i] == girilen_deger) # girilen değer xss paylodına eşitse
sql_kontrol = lambda girilen_deger: any(sql_payload[i] for i in sql_index if sql_payload[i] == girilen_deger) # girilen değer sql payloadına eşitse
# ===========================
def tara(orjinal_input): # zararlı girdi tespit edilirse devam_dur değişkenini dur'a eşitliycez ve sql sorgusu yapmıycaz ama sql sorgusu yapmış gibi sonuç döndürücez
zamanı_artır = 0
devam_dur = "devam"
input_incele = orjinal_input.split()
if len(input_incele) > 1: # 1 elemandan fazla ise demek (yani boşluklu girdi yapıldıysa ama bu boşluk sağdaki ve soldaki önemsiz boşluklar değil 'mer haba' tarzı boşlukdan bahsediyorum)
devam_dur = "dur"
zamanı_artır = 0.003 # ilk denemede zararlı girdi bulunduysa 3ms'lik bir bekleme süresi ekliycez
else:
if özel_karakter_kontrol(orjinal_input):
devam_dur = "dur"
zamanı_artır = 0.002 # 2ms bekleme süresi
else:
if xss_kontrol(orjinal_input):
devam_dur = "dur"
zamanı_artır = 0.001 # 1ms bekleme süresi
else:
if sql_kontrol(orjinal_input): # burasıda çalışırsa zaten kodun en uzun çalışma süresindeki hali kullanıcıya yansımış olur yani süreyi artırmaya gerek yok
devam_dur = "dur"
zamanı_artır = 0
return devam_dur,zamanı_artır
# =====================
second = Blueprint("second",__name__)
@second.route("/register/", methods=['GET', 'POST'])
def register_sayfası():
register_mesaj = ""
if request.method=='POST': # post işlemi yapıldıysa yani input değerleri girilip enter'a yada gönder'e basıldıysa burası çalışcak
# return kısmındaki name değerlerini çekicez
username = request.form['username'] # name="username" kısmına girilen değeri çekiyoruz
password = request.form['password'] # name="password" kısmına girilen değeri çekiyoruz
username = username.strip() # sağdaki soldaki gereksiz boşlukları kaldırıyoruz
password = password.strip()
if not username or not password: # kullanıcının boş bıraktığı input alanı varsa
register_mesaj = "lütfen tüm alanları doldurun"
# kullanıcı boş girdi yaptıysa beklemeden cevap verebiliriz herhangi bir sorun olmaz
else:
devam_dur,zamanı_artır = tara(username) # güvenlik taraması
rastgele_süre = random.uniform(0,zamanı_artır) # fonksiyonun bize döndürdüğü zaman 3ms diyelim 0 ile 3ms arasından rastgele bir süre alıp o zaman kadar bekleme yapıcaz
if devam_dur == "dur": # zararlı girdi tespit edildiyse devam_dur değişkeni dur'a eşitlenir eğer dur'a eşitlendiyse mesajımızı veriyoruz
# ve alt tarafta zamanın geçmesini bekleyip zaman geçtikten sonra return tarafında cevabımızı kullanıcıya döndürüyoruz
register_mesaj = "kullanıcı ve password değerlerinde özel karakter ve boşluk kullanmayın"
else:
# username'de ve password'da herhangi bir sorun yoksa buraları çalıştıracaz
baglan = sqlite3.connect(db_name) # database'e bağlanıyoruz
cursor = baglan.cursor()
kullanici_mevcutmu = f"SELECT COUNT(*) FROM users WHERE name = '{username}';" # kullanıcı database'de varmı kontrol ediyoruz eğer yoksa 0 değeri döner
cursor.execute(kullanici_mevcutmu) # execute, sql komutunu çalıştırır
if cursor.fetchall()[0][0] == 0: # kullanıcı yoksa
kullanici_ekle = f"INSERT INTO users VALUES ('{username}','{password}');" # kullanıcıyı ekliyoruz
cursor.execute(kullanici_ekle)
baglan.commit()
register_mesaj = "kullanıcı oluşturuldu"
else:
register_mesaj = "bu kullanıcı mevcut"
cursor.close() # bağlantıları kapatıyoruz
baglan.close()
time.sleep(rastgele_süre) # rastgele süre kadar beliyoruz
return '''
<style>
h2 {text-align: center}
form {text-align: center}
</style>
<h2>kullanıcı oluşturun</h2>
<br>
<form id="gönder" action="/register/" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="gönder">
</form>
<br>
'''+'''
<h2>{}</h2>
'''.format(register_mesaj)
# ========================
second1 = Blueprint("second1",__name__)
@second1.route("/login/", methods=['GET', 'POST'])
def login_sayfası():
login_mesaj = ""
if request.method=='POST':
username = request.form['username']
username = username.strip() # sql sorgusunda girilen input değerinde boşluk varsa doğru sonuç dönmez o yüzden boşlukları kaldırıp sql sorgusuna öyle dahil ediyoruz
if not username:
login_mesaj = "kullanıcı mevcut değil"
# username tarafına bir girdi yapılmadıysa yine beklememize gerek yok direk cevap döndürücez
else:
devam_dur,zamanı_artır = tara(username)
rastgele_süre = random.uniform(0,zamanı_artır)
if devam_dur == "dur":
login_mesaj = "kullanıcı mevcut değil"
else:
baglan = sqlite3.connect(db_name)
cursor = baglan.cursor()
kullanici_bilgisi = f"SELECT * FROM users WHERE name = '{username}';" # kullanıcı bilgilerini çeken bir kod
cursor.execute(kullanici_bilgisi)
rows = cursor.fetchall()
if not rows: # kullanıcı mevcut değilse boş bir liste gelir
login_mesaj = "kullanıcı mevcut değil"
else: # kullanıcı mevcutsa bilgiler liste içinde gelir
rows = rows[0]
login_mesaj = rows # kullanıcı bilgileri
cursor.close()
baglan.close()
time.sleep(rastgele_süre) # rastgele süre kadar bekliyoruz
# ve bekleme sonrasında return ile sayfamızı döndürüyoruz
return '''
<style>
h2 {text-align: center}
form {text-align: center}
</style>
<h2>şifrenizi öğrenin</h2>
<br>
<form id="gönder" action="/login/" method="POST">
<input type="text" name="username">
<input type="submit" value="gönder">
</form>
<br>
'''+'''
<h2>{}</h2>
'''.format(login_mesaj)