Skip to content

34. Uygulama alıştırması: sürüm 14

Image

Sürüm 14'teki [http-servers/09] dosyası, sürüm 13'teki [http-servers/08] dosyasının kopyalanmasıyla elde edilir.

34.1. Giriş

CSRF (Cross Site Request Forgery), bir oturum çalma tekniğidir. Wikipedia'da (https://fr.wikipedia.org/wiki/Cross-site_request_forgery) şu şekilde açıklanmaktadır:

Alice'in bir forumun yöneticisi olduğunu ve oturum sistemi aracılığıyla bu foruma giriş yapmış olduğunu varsayalım. Malorie ise aynı forumun bir üyesidir ve forumdaki mesajlardan birini silmek istemektedir. Kendi hesabıyla gerekli yetkilere sahip olmadığı için, CSRF türünde bir saldırı gerçekleştirerek Alice'in hesabını kullanır.
  1. Malorie, söz konusu mesajı silmeye yarayan bağlantıyı öğrenmeyi başarır.
  2. Malorie, Alice’e görüntülenmesi gereken sahte bir resim (aslında bir komut dosyası) içeren bir mesaj gönderir. Resimdeki URL, istenen mesajı silmeye yarayan komut dosyasına giden bağlantıdır.
  3. Alice’in tarayıcısında Malorie’nin hedeflediği siteye ait açık bir oturum bulunmalıdır. Bu, Alice’i uyandıracak bir kimlik doğrulama isteği gerektirmeden saldırının sessizce başarılı olması için gerekli bir koşuldur. Bu oturum, Malorie’nin yıkıcı isteğini yürütmek için gerekli yetkilere sahip olmalıdır. Hedef sitede bir tarayıcı sekmesinin açık olması veya hatta tarayıcının başlatılmış olması gerekmez. Oturumun aktif olması yeterlidir.
  4. Alice, Malorie’nin mesajını okur; tarayıcısı, Alice’in açık oturumunu kullanır ve etkileşimli kimlik doğrulama istemez. Tarayıcı, resmin içeriğini almaya çalışır. Bunu yaparken tarayıcı, bağlantıyı etkinleştirir ve mesajı siler; resmin içeriği olarak metin içeren bir web sayfası alır. İlişkili resim türünü tanımadığı için resim görüntülenmez ve Alice, Malorie’nin az önce kendi rızası dışında bir mesajı sildirdiğinden haberdar olmaz.

Böyle açıklansa bile, CSRF tekniğini anlamak zordur. Bir şema çizelim:

Image

  • [1-2]'te Alice, forumla (Site A) iletişim kurar. Bu forum, her kullanıcı için bir oturum tutar. Alice’in tarayıcısı bu oturum çerezini yerel olarak depolar ve Site A’ya her yeni istek gönderdiğinde onu geri gönderir;
  • [3] örneğinde, Malorie Alice’e bir mesaj gönderir. Alice bu mesajı tarayıcısıyla okur. Okunan mesaj HTML biçimindedir ve Site B’deki bir resme yönlendiren bir bağlantı içerir. Aslında bu bağlantı, Alice’in tarayıcısına ulaştığında çalışan bir JavaScript betiğine yönlendiren bir bağlantıdır;
  • bu JavaScript betiği daha sonra A sitesine bir istek gönderir. Alice’in tarayıcısı, yerel olarak depolanan oturum çerezi ile birlikte bu isteği otomatik olarak gönderir. Saldırı işte bu noktada gerçekleşir: Malorie, Alice’in haklarıyla (oturumuyla) A sitesine erişmeyi başarmıştır. Bundan sonra ne olursa olsun, saldırı gerçekleşmiştir;

Bu tür saldırıları önlemek için A sitesi şu şekilde hareket edebilir:

  • Alice ile her [1-2] alışverişinde, A sitesi daha sonra CSRF tokeni olarak adlandırılan bir anahtar gönderir ve Alice'in bir sonraki isteğinde bunu geri göndermesi gerekir. Böylece Alice, her istekte iki bilgiyi göndermelidir:
    • oturum çerezi;
    • A sitesine yaptığı son isteğe verilen yanıtta aldığı CSRF tokeni;

Güvenlik önlemi şurada yatmaktadır: Tarayıcı, oturum çerezini Site A’ya otomatik olarak geri gönderir, ancak CSRF jetonunu göndermez. Bu nedenle, saldırı komut dosyası tarafından gerçekleştirilen 6-7 numaralı iletişim reddedilecektir, çünkü 6 numaralı istek CSRF jetonunu göndermeyecektir;

A sitesi, HTML uygulaması için Alice’e CSRF jetonunu çeşitli şekillerde gönderebilir:

  • her istekte, tüm bağlantıların CSRF jetonunu içerdiği bir HTML sayfası gönderebilir; örneğin [http://siteA/chemin/csrf_token]. Bir sonraki istek sırasında, Alice bu bağlantılardan birine tıkladığında, A sitesi isteğin URL'sinden CSRF jetonunu alıp bunun doğru olup olmadığını kontrol etmekle yetinecektir. Burada da bu şekilde yapılacaktır;
  • Form içeren HTML sayfaları için, CSRF jetonunu içeren [input type=’hidden’] adlı gizli bir alanla birlikte bu formu gönderebilir. Alice sayfayı onayladığında bu jeton, formla birlikte otomatik olarak gönderilecektir. A sitesi, isteğin gövdesinde (body) CSRF jetonunu alacaktır;
  • başka teknikler de düşünülebilir;

34.2. Yapılandırma

Image

Uygulamanın [parameters] yapılandırmasına iki boole değeri ekliyoruz:

  • [with_redissession]: True olarak ayarlandığında, uygulama bir Redis oturumu kullanır. False olarak ayarlandığında, uygulama normal bir Flask oturumu kullanır;
  • [with_csrftoken]: True olarak ayarlandığında, uygulamanın URL'leri bir CSRF jetonu içerir;

        # iş parçacığı bekleme süresi (saniye cinsinden)
        "sleep_time"0,
        # Redis sunucusu
        "with_redissession"True,
        "redis": {
            "host""127.0.0.1",
            "port"6379
        },
        # CSRF jetonu
        "with_csrftoken"False,

34.3. CSRF Uygulaması

Aşağıdaki durumlarda:


config['parameters']['with_csrftoken']

değeri [True] olduğunda, uygulamanın istemci tarayıcısına, bağlantılarında CSRF jetonu bulunan web sayfaları göndermesini sağlayacağız.

34.3.1. [flask_wtf] modülü

CSRF jetonunun uygulaması, bir PyCharm terminaline kurduğumuz [flask_wtf] modülü ile gerçekleştirilecektir:


(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install flask_wtf
Collecting flask_wtf

34.3.2. Görünüm şablonları

Şablonlara yeni bir sınıf ekliyoruz:

Image

[AbstractBaseModelForView] sınıfı şu şekildedir:


from abc import abstractmethod

from flask import Request
from flask_wtf.csrf import generate_csrf
from werkzeug.local import LocalProxy

from InterfaceModelForView import InterfaceModelForView

class AbstractBaseModelForView(InterfaceModelForView):

    @abstractmethod
    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        pass

    def get_csrftoken(self, config: dict):
        # csrf_token
        if config['parameters']['with_csrftoken']:
            return f"/{generate_csrf()}"
        else:
            return ""
  • 9. satır: [AbstractBaseModelForView] sınıfı, şablon sınıfları tarafından uygulanan [InterfaceModelForView] arayüzünü uygular;
  • 11-13. satırlar: [get_model_for_view] yöntemi uygulanmamıştır;
  • 15-20. satırlar: Uygulama bu jetonları kullanmak üzere yapılandırılmışsa, [get_csrftoken] yöntemi CSRF jetonunu oluşturur. Duruma göre, işlev jetonun önüne / işareti ekleyerek bir jeton döndürür; aksi takdirde boş bir dize döndürür. [generate_csrf] işlevinin özelliği, belirli bir istemci isteği için her zaman aynı değeri üretmesidir. Bir isteğin işlenmesi, farklı işlevlerin yürütülmesini gerektirir. Bu işlevlerde [generate_csrf] kullanıldığında her zaman aynı değer üretilir. Buna karşın, bir sonraki istekte yeni bir CSRF jetonu üretilir;

V görünümünün tüm M şablonları, CSRF jetonunu aşağıdaki şekilde içerecektir:


class ModelForAuthentificationView(AbstractBaseModelForView):

    def get_model_for_view(self, request: Request, session: LocalProxy, config: dict, résultat: dict) -> dict:
        # sayfa verilerini şablona yerleştiriyoruz
        modèle = {}
        

        # CSRF jetonu
        modèle['csrf_token'] = super().get_csrftoken(config)

        # şablon gönderilir
        return modèle
  • her şablon sınıfı, [AbstractBaseModelForView] temel sınıfını genişletir;
  • 8. satır: üst sınıftan CSRF jetonu istenir. Sonuç olarak ya boş bir dize ya da [/Ijk4NjQ2ZDdjZjI0ZDJiYTVjZTZjYmFhZGNjMjE3Y2U5M2I3ODI0NzYi.Xy5Okg.n-kSR_nslkndfT7AFVy2UDtdb8c] gibi bir dize elde edilir;

34.3.3. Görünümler

Az önce gördüğümüz gibi, tüm V görünümlerinin M şablonlarında CSRF jetonu bulunacaktır. Dolayısıyla, içerdikleri bağlantılarda bu jetonu kullanabilirler. Birkaç örnek verelim:

[v_authentification.html] kimlik doğrulama parçası


<!-- HTML formu - değerleri [authentifier-utilisateur] eylemiyle gönderilir -->
<form method="post" action="/authentifier-utilisateur{{modèle.csrf_token}}">

    <!-- başlık -->
    <div class="alert alert-primary" role="alert">
        <h4>Veuillez vous authentifier</h4>
    </div>


</form>
  • 2. satır: az önce gördüğümüz gibi, [action] özniteliğinin URL değeri şu şekilde olacaktır:

[/authentifier-utilisateur/Ijk4NjQ2ZDdjZjI0ZDJiYTVjZTZjYmFhZGNjMjE3Y2U5M2I3ODI0NzYi.Xy5Okg.n-kSR_nslkndfT7AFVy2UDtdb8c]

veya

[/authentifier-utilisateur]

uygulamanın CSRF jetonlarını kullanacak şekilde yapılandırılıp yapılandırılmadığına bağlı olarak;

[v-calcul-impot.html] vergi hesaplama parçası


<!-- gönderilen HTML formu -->
<form method="post" action="/calculer-impot{{modèle.csrf_token}}">
    <!-- mavi arka planlı 12 sütunlu mesaj -->
    <div class="col-md-12">
        <div class="alert alert-primary" role="alert">
            <h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
        </div>
    </div>
    
</form>

Simülasyon parçası [v-liste-simulations.html]


{% if modèle.simulations is undefined or modèle.simulations|length==0 %}
<!-- mavi arka planlı mesaj -->
<div class="alert alert-primary" role="alert">
    <h4>Votre liste de simulations est vide</h4>
</div>
{% endif %}

{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
<!-- mavi arka planlı mesaj -->
<div class="alert alert-primary" role="alert">
    <h4>Liste de vos simulations</h4>
</div>

<!-- simülasyon tablosu -->
<table class="table table-sm table-hover table-striped">
    
    <!-- tablonun gövdesi (görüntülenen veriler) -->
    <tbody>
    <!-- simülasyon tablosunu tarayarak her bir simülasyon görüntülenir -->
    {% for simulation in modèle.simulations %}

    <!-- 6 sütunlu tablonun bir satırının görüntülenmesi - <tr> etiketi -->
    <!-- 1. sütun: satır başlığı (simülasyon numarası) - <th scope='row'> etiketi -->
    <!-- 2. sütun: parametre değeri [marié] - <td> etiketi -->
    <!-- 3. sütun: [enfants] parametre değeri - <td> etiketi -->
    <!-- 4. sütun: [salaire] parametre değeri - <td> etiketi -->
    <!-- 5. sütun: [impôt] parametre değeri (vergi) - <td> etiketi -->
    <!-- 6. sütun: [surcôte] parametre değeri - <td> etiketi -->
    <!-- 7. sütun: [décôte] parametre değeri - <td> etiketi -->
    <!-- 8. sütun: [réduction] parametre değeri - <td> etiketi -->
    <!-- 9. sütun: [taux] parametre değeri (vergi) - <td> etiketi -->
    <!-- 10. sütun: simülasyonu silme bağlantısı - <td> etiketi -->
    <tr>
        <th scope="row">{{simulation.id}}</th>
        <td>{{simulation.marié}}</td>
        <td>{{simulation.enfants}}</td>
        <td>{{simulation.salaire}}</td>
        <td>{{simulation.impôt}}</td>
        <td>{{simulation.surcôte}}</td>
        <td>{{simulation.décôte}}</td>
        <td>{{simulation.réduction}}</td>
        <td>{{simulation.taux}}</td>
        <td><a href="/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
    </tr>
    {% endfor %}
    </tr>
    </tbody>
</table>
{% endif %}

Menü parçası [v-menu.html]


<!-- Bootstrap menüsü -->
<nav class="nav flex-column">
    <!-- bağlantı listesinin görüntülenmesi HTML -->
    {% for optionMenu in modèle.optionsMenu %}
    <a class="nav-link" href="{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
    {% endfor %}
</nav>

34.3.4. Yollar

Artık, CSRF jetonu kullanıp kullanmadıklarına göre iki tür yol bulunmaktadır:

Image

  • [routes_without_csrftoken], CSRF jetonu kullanılmayan yollardır. Bunlar önceki sürümdeki yollardır;
  • [routes_with_csrftoken], CSRF jetonunu kullanan yollardır.

[routes_with_csrftoken]'te rotalar artık ek bir parametreye sahiptir: CSRF jetonu:


# ön denetleyici
def front_controller() -> tuple:
    # istek ana denetleyiciye iletilir
    main_controller = config['mvc']['controllers']['main-controller']
    return main_controller.execute(request, session, config)

@app.route('/', methods=['GET'])
def index() -> tuple:
    # /init-session/html adresine yönlendirme
    return redirect(url_for("init_session", type_response="html"csrf_token=generate_csrf()), status.HTTP_302_FOUND)

# init-session
@app.route('/init-session/<string:type_response>/<string:csrf_token>', methods=['GET'])
def init_session(type_response: str, csrf_token: str) -> tuple:
    # eylemle ilişkili denetleyici çalıştırılıyor
    return front_controller()

# kullanıcı kimlik doğrulaması
@app.route('/authentifier-utilisateur/<string:csrf_token>', methods=['POST'])
def authentifier_utilisateur(csrf_token: str) -> tuple:
    # eylemle ilişkili denetleyici çalıştırılıyor
    return front_controller()

# vergi-hesapla
@app.route('/calculer-impot/<string:csrf_token>', methods=['POST'])
def calculer_impot(csrf_token: str) -> tuple:
    # eyleme ilişkin denetleyici çalıştırılır
    return front_controller()

# toplu vergi hesaplaması
@app.route('/calculer-impots/<string:csrf_token>', methods=['POST'])
def calculer_impots(csrf_token: str):
    # eyleme bağlı denetleyici çalıştırılıyor
    return front_controller()

# simülasyonları listeleme
@app.route('/lister-simulations/<string:csrf_token>', methods=['GET'])
def lister_simulations(csrf_token: str) -> tuple:
    # eyleme bağlı denetleyici çalıştırılır
    return front_controller()

# simülasyonu sil
@app.route('/supprimer-simulation/<int:numero>/<string:csrf_token>', methods=['GET'])
def supprimer_simulation(numero: int, csrf_token: str) -> tuple:
    # eyleme ilişkin denetleyici çalıştırılır
    return front_controller()

# oturum sonu
@app.route('/fin-session/<string:csrf_token>', methods=['GET'])
def fin_session(csrf_token: str) -> tuple:
    # eyleme ilişkin denetleyici çalıştırılıyor
    return front_controller()

# vergi-hesaplamasını-göster
@app.route('/afficher-calcul-impot/<string:csrf_token>', methods=['GET'])
def afficher_calcul_impot(csrf_token: str) -> tuple:
    # eyleme ilişkin denetleyici çalıştırılıyor
    return front_controller()

# yönetici verilerini al
@app.route('/get-admindata/<string:csrf_token>', methods=['GET'])
def get_admindata(csrf_token: str) -> tuple:
    # eyleme bağlı denetleyici çalıştırılıyor
    return front_controller()

Artık tüm rotaların parametrelerinde CSRF jetonu bulunmaktadır; [/init-session] rotası da dahil. Bu, müşterinin URL veya [/init-session/html]'i doğrudan yazarak uygulamayı başlatamayacağı anlamına gelir, çünkü CSRF jetonu eksik olacaktır. Artık mutlaka 7-10. satırlardaki URL ve [/] yolunu kullanması gerekir.

Rota seçimi, ana komut dosyası [main] içinde yapılır:



# ana iş parçacığı artık logger'a ihtiyaç duymuyor
logger.close()

# hata oluşursa işlem durdurulur
if erreur:
    sys.exit(2)

# web uygulamasının rotaları içe aktarılır
if config['parameters']['with_csrftoken']:
    import routes_with_csrftoken as routes
else:
    import routes_without_csrftoken as routes

# rotaların yapılandırılması
routes.config = config

# Flask uygulamasını başlat
routes.execute(__name__)
  • 9-13. satırlar: Uygulamanın CSRF jetonlarını kullanıp kullanmadığına göre rota seçimi;

34.3.5. [MainController] denetleyicisi

Her istek geldiğinde, sunucu CSRF jetonunun varlığını kontrol etmelidir. Bunu, tüm isteklerin geçtiği ana denetleyici [MainController] içinde yapacağız:


from flask_wtf.csrf import generate_csrf, validate_csrf

       # isteği işleniyor
        try:
            # günlük kaydı
            logger = Logger(config['parameters']['logsFilename'])

            …

            # yolun öğeleri alınır
            params = request.path.split('/')

            # eylem, yolun ilk öğesidir
            action = params[1]

            …

            if config['parameters']['with_csrftoken']:
                # csrf_token, yolun son öğesidir
                csrf_token = params.pop()
                # token'ın geçerliliği kontrol edilir
                # csrf_token beklenenle aynı değilse bir istisna atılır
                validate_csrf(csrf_token)

            …

        except ValidationError as exception:
            # CSRF jetonu geçersiz
            résultat = {"action": action, "état"121"réponse"[f"{exception}"]}
            status_code = status.HTTP_400_BAD_REQUEST

        except BaseException as exception:
            # diğer (beklenmeyen) istisnalar
            résultat = {"action": action, "état"131"réponse"[f"{exception}"]}
            status_code = status.HTTP_400_BAD_REQUEST

        finally:
            pass

        # sonuca csrf_token eklenir
        résultat['csrf_token'] = generate_csrf()

        # müşteriye gönderilen sonuç günlüğe kaydediliyor
        log = f"[MainController] {résultat}\n"
        logger.write(log)
  • 20. satır: [http://machine :port/chemin/action/param1/param2/…/csrf_token] türündeki isteğin URL'sinden CSRF jetonunu alıyoruz. Oturum jetonu her zaman URL'in son öğesidir;
  • 23. satır: URL'ten alınan CSRF jetonunun, oturumun CSRF jetonuyla geçerliliği kontrol edilir. Geçerli değilse, [validate_csrf] işlevi [ValidationError] türünde bir istisna oluşturur (satır 27);
  • 41. satır: CSRF jetonu, müşteriye gönderilen sonuca eklenir. jSON ve XML istemcileri buna ihtiyaç duyacaktır. Zira bu istemciler, sayfalarda yer alan bağlantılarda CSRF jetonunu içeren HTML sayfalarını almazlar. Dolayısıyla, bu jetonu sunucu tarafından gönderilen jSON veya XML sonucunda alacaklardır;

Not: 23. satırdaki [validate_csrf] işlevi, tam bir eşleşmeyi kontrol etmez. CSRF jetonu, [csrf_token] anahtarıyla oturumda saklanır. Testler, bir CSRF jetonunun oturum sırasında oluşturulmuş olması durumunda geçerli olduğunu gösteriyor gibi görünüyor. Dolayısıyla, örneğin tarayıcıda görüntülenen URL adresinde (/lister-simulations/xyz) manuel olarak [xyz] veya CSRF jetonunu, önceki bir işlem sırasında zaten alınmış olan [abc] jetonuyla değiştirirseniz, [/lister-simulations] işlemi başarılı olacaktır;

34.4. Tarayıcı ile testler

Şu şekilde:

  • sunucuyu [with_csrftoken] ile [True] parametresiyle başlatın;
  • bir tarayıcıyla URL ve [http://localhost:5000] adreslerini çağırın;

Image

  • [1]'te, CSRF jetonunu;

Simülasyon listesi elde edene kadar işlemleri tekrarlayalım:

Image

Şimdi, id=1 olan simülasyonu silmek için URL [http://localhost:5000/supprimer-simulation/1/x] komutunu elle girelim. Ne olacağını görmek için kasıtlı olarak hatalı bir CSRF jetonu giriyoruz. Sunucunun yanıtı şöyledir:

Image

Not 1: Burada kullanılan yöntemin, CSRF saldırılarını her zaman engellemek için yeterli olacağı kesin değildir. Saldırı şemasına geri dönelim:

Image

[5] olarak indirilen JavaScript betiği, Alice’in kullandığı tarayıcının geçmişini okuyabiliyorsa, tarayıcı tarafından yürütülen URL'leri ve [/cible/csrf_token] gibi URL'leri ele geçirebilir. Böylece [csrf_token] oturum jetonunu ele geçirebilir ve [6-7] üzerinden saldırısını gerçekleştirebilir. Ancak tarayıcı, yalnızca komut dosyasının çalıştırıldığı tarayıcı penceresinin geçmişinin kullanılmasına izin verir. Dolayısıyla, Alice, A sitesiyle çalışmak için [1-2] penceresini ve Malorie’nin mesajını okumak için [3] penceresini aynı pencereyi kullanmazsa, CSRF saldırısı mümkün olmayacaktır.

34.5. Konsol istemcileri

Uygulamanın 14. sürümünü test etmenin bir başka yolu da 12. sürümdeki testleri alıp yeni sunucuya uyarlamaktır.

Image

[impots/http-clients/09] dosyası, başlangıçta [impots/http-clients/07] dosyasının kopyalanmasıyla elde edilir. Daha sonra bu dosya değiştirilir.

Bir oturumu başlatan rotalara geri dönelim:


# uygulamanın kök dizini
@app.route('/', methods=['GET'])
def index() -> tuple:
    # /init-session/html adresine yönlendirme
    return redirect(url_for("init_session", type_response="html", csrf_token=generate_csrf()), status.HTTP_302_FOUND)

# init-session-with-csrf-token
@app.route('/init-session/<string:type_response>/<string:csrf_token>', methods=['GET'])
def init_session(type_response: str, csrf_token: str) -> tuple:
    # eylemle ilişkili denetleyici çalıştırılıyor
    return front_controller()

Bu rotaların hiçbiri, jSON veya XML oturumunu başlatmak için uygun değildir:

  • 2-5. satırlar: [/] rotası, HTML oturumunu başlatır;
  • 8-11. satırlar: [/init-session] rotası, bilinmeyen bir CSRF jetonunu gerektirir;

Sunucuya yeni bir rota eklemeye karar veriyoruz:


# csrftoken olmadan oturum açma
@app.route('/init-session-without-csrftoken/<string:type_response>', methods=['GET'])
def init_session_without_csrftoken(type_response: str) -> tuple:
    # /init-session/type_response adresine yönlendirme
    return redirect(url_for("init_session", type_response=type_response, csrf_token=generate_csrf()), status.HTTP_302_FOUND)
  • 2. satır: yeni rota. Bu rota, CSRF jetonunu beklemiyor. Böylece önceki sürümdeki [/init-session] rotasına geri döndük;
  • 4-5. satırlar: istemciyi yeniden yönlendiriyoruz (jSON, XML, HTML) parametrelerinde CSRF jetonunu içeren [/init-session] rotasına yönlendiriliyor;

Bu yeni rotayı bir tarayıcıyla deneyebilirsiniz:

Image

([with_csrftoken=True] ile yapılandırılmış) sunucunun yanıtı şöyledir:

Image

  • [1]'te, sunucu [/init-session] yoluna, CSRF jetonu ile URL'te yeniden yönlendirilmiştir;
  • [2]'te, CSRF jetonu, [csrf_token] anahtarıyla ilişkili sunucu tarafından gönderilen jSON sözlüğünde yer almaktadır;

İstemci koduna geri dönelim:

Image

[config] yapılandırmasını şu şekilde değiştiriyoruz:


   config.update({
        # vergi mükellefleri dosyası
        "taxpayersFilename"f"{script_dir}/../data/input/taxpayersdata.txt",
        # sonuç dosyası
        "resultsFilename"f"{script_dir}/../data/output/résultats.json",
        # hata dosyası
        "errorsFilename"f"{script_dir}/../data/output/errors.txt",
        # günlük dosyası
        "logsFilename"f"{script_dir}/../data/logs/logs.txt",
        # vergi hesaplama sunucusu
        "server": {
            "urlServer""http://127.0.0.1:5000",
            "user": {
                "login""admin",
                "password""admin"
            },
            "url_services": {
                "calculate-tax""/calculer-impot",
                "get-admindata""/get-admindata",
                "calculate-tax-in-bulk-mode""/calculer-impots",
                "init-session""/init-session-without-csrftoken",
                "end-session""/fin-session",
                "authenticate-user""/authentifier-utilisateur",
                "get-simulations""/lister-simulations",
                "delete-simulation""/supprimer-simulation",
            }
        },
        # hata ayıklama modu
        "debug"True,
        # csrf_token
        "with_csrftoken"True,
    }
    )

    # init-session yolu
    url_services = config['server']['url_services']
    if config['with_csrftoken']:
        url_services['init-session'] = '/init-session-without-csrftoken'
    else:
        url_services['init-session'] = '/init-session'
  • 31. satır: Bir boole değeri, istemciye, bağlantı kurduğu sunucunun CSRF jetonlarıyla çalışıp çalışmadığını gösterecektir;
  • 37-40. satırlar: [init-session] eyleminin hizmet URL'i ayarlanır:
    • sunucu CSRF jetonlarını kullanıyorsa, hizmet URL değeri [/init-session-without-csrftoken] olur;
    • aksi takdirde URL hizmet kodu [/init-session] olur;

[/init-session-without-csrftoken] yolu sunulmuştur. Bu yol, jSON / XML istemcisinin CSRF jetonuna sahip olmadan sunucuyla oturum açmasına olanak tanır. Müşteri, bu jetonu sunucunun yanıtında bulacaktır.

Ardından, müşterinin [dao] katmanını uygulayan [ImpôtsDaoWithHttpSession] sınıfını değiştiriyoruz:

Image


# ithalatlar
import json

import requests
import xmltodict
from flask_api import status

from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsDaoWithHttpSession import InterfaceImpôtsDaoWithHttpSession
from TaxPayer import TaxPayer

class ImpôtsDaoWithHttpSession(InterfaceImpôtsDaoWithHttpSession):

    # yapıcı
    def __init__(self, config: dict):
        # üst nesnenin başlatılması
        AbstractImpôtsDao.__init__(self, config)
        # yapılandırma öğelerinin depolanması
        # genel yapılandırma
        self.__config = config
        # sunucu
        self.__config_server = config["server"]
        # hizmetler
        self.__config_services = config["server"]['url_services']
        # hata ayıklama modu
        self.__debug = config["debug"]
        # günlük kaydı
        self.__logger = None
        # çerezler
        self.__cookies = None
        # oturum türü (json, xml)
        self.__session_type = None
        # jeton CSRF
        self.__csrf_token = None

    # istek / yanıt aşaması
    def get_response(self, method: str, url_service: str, data_value: dict = None, json_value=None):
        # [method] : HTTP, GET veya POST yöntemi
        # [url_service] : URL hizmet
        # [data] : POST'in x-www-form-urlencoded biçimindeki parametreleri
        # [json] : POST parametreleri (json formatında)
        # [cookies]: isteğe eklenecek çerezler

        # XML veya JSON oturumu olmalıdır, aksi takdirde yanıtı işleyemeyiz
        if self.__session_type not in ['json''xml']:
            raise ImpôtsError(73"il n'y a pas de session valide en cours")

        # URL hizmet oturumuna CSRF jetonu eklenir
        if self.__csrf_token:
            url_service = f"{url_service}/{self.__csrf_token}"

        # isteğin yürütülmesi
        response = requests.request(method,
                                    url_service,
                                    data=data_value,
                                    json=json_value,
                                    cookies=self.__cookies,
                                    allow_redirects=True)

        # hata ayıklama modu mu?
        if self.__debug:
            # günlük kaydedici
            if not self.__logger:
                self.__logger = self.__config['logger']
            # günlüğe kaydediliyor
            self.__logger.write(f"{response.text}\n")

        # sonuç
        if self.__session_type == "json":
            résultat = json.loads(response.text)
        else:  # xml
            résultat = xmltodict.parse(response.text[39:])['root']

        # varsa yanıtta bulunan çerezleri alıyoruz
        if response.cookies:
            self.__cookies = response.cookies

        # jetonu alınır CSRF
        if self.__config['with_csrftoken']:
            self.__csrf_token = résultat.get('csrf_token'None)

        # durum kodu
        status_code = response.status_code

        # durum kodu 200'den farklıysa OK
        if status_code != status.HTTP_200_OK:
            raise ImpôtsError(35, résultat['réponse'])

        # sonucu döndürüyoruz
        return résultat['réponse']

    
    def init_session(self, session_type: str):
        # oturum türünü kaydedilir
        self.__session_type = session_type

        # önceki çağrılardan CSRF jetonunu silin
        self.__csrf_token = None

        # init-session eyleminin URL'ini talep ediyoruz
        url_service = f"{self.__config_server['urlServer']}{self.__config_services['init-session']}/{session_type}"

        # isteğin yürütülmesi
        self.get_response("GET", url_service)

  • 38-92. satırlar: CSRF jetonunun yönetimi esas olarak [get_response] yönteminde gerçekleştirilir;
  • 60. satır: Önemli nokta, [allow_redirects=True] parametresidir. Bu, parametrenin varsayılan değeridir ancak özellikle vurgulanması istenmiştir;

[with_csrftoken=True] modundayken:

  • istemciler, sunucuyla iletişim kurmaya [/init-session_without_csftoken/type_response] yolunu çağırarak başlar;
  • sunucu bu isteğe, [/init-session/type_response/csrf_token] rotasına bir yönlendirme ile yanıt verir;
  • [allow_redirects=True] parametresi nedeniyle, bu yönlendirme [requests] istemcisi tarafından takip edilecektir;
  • CSRF jetonu, 72. ve 74. satırlarda alınan sonuçta [csrf_token] anahtarıyla ilişkili olarak bulunacaktır;

[with_csrftoken=False] modundayken:

  • (devam)
    • istemciler, sunucuyla diyaloglarına [/init-session /type_response] yolunu çağırarak başlar;
    • sunucu bu isteğe, [/init-session/type_response] rotasına bir yönlendirme ile yanıt verir;
    • [allow_redirects=True] parametresi nedeniyle, bu yönlendirme istemci tarafından [requests] olarak takip edilecektir;
    • 81-82. satırlarda alınacak CSRF jetonu yoktur. Dolayısıyla [self.__csrf_token] özelliği her zaman None olarak kalır (36. satır);
  • 51-52. satırlar: sonraki tüm istekler için, CSRF jetonu, varsa, başlangıç rotasına eklenir;
  • satır 81-82: Sunucu, istemciden gelen her yeni istek için oluşturduğu yeni jetonu, bir sonraki istek için satır 52'de geri gönderilmek üzere yerel olarak saklar;

Ayrıca, [init_session] yöntemi biraz değişir:


    def init_session(self, session_type: str):
        # oturum türü kaydediliyor
        self.__session_type = session_type

        # önceki çağrılardan CSRF jetonu siliniyor
        self.__csrf_token = None

        # init-session eyleminin URL'i isteniyor
        url_service = f"{self.__config_server['urlServer']}{self.__config_services['init-session']}/{session_type}"

        # isteğin yürütülmesi
        self.get_response("GET", url_service)

Burada, CSRF jetonu olmadan istemci/sunucu iletişimini başlatmak için [/init-session-without-csrftoken/<type-response>] rotasının oluşturulduğunu hatırlamak gerekir. Ancak, kodun 12. satırında çağrılan [get_response] yönteminin, [self.__csrf_token]'te depolanan CSRF jetonunu sistematik olarak URL hizmetinin sonuna eklediğini görmüştük. Bu nedenle kodun 6. satırında, varsa bu CSRF jetonu siliniyor.

Hepsi bu kadar. Test amacıyla şu komutu çalıştıracağız:

  • [main, main2, main3] konsol istemcileri;
  • [Test1HttpClientDaoWithSession] ve [Test2HttpClientDaoWithSession] test sınıfları;

[with_csrftoken] yapılandırma parametresini sırasıyla True ve ardından False olarak ayarlayacağız.

Image

Aşağıda, [main json] istemcisinin [with_csrftoken=True] ile çalıştırılması sırasında elde edilen günlük örnekleri yer almaktadır:


2020-08-08 16:33:23.317903, MainThread : début du calcul de l'impôt des contribuables
2020-08-08 16:33:23.317903, Thread-1 : début du calcul de l'impôt des 4 contribuables
2020-08-08 16:33:23.317903, Thread-2 : début du calcul de l'impôt des 2 contribuables
2020-08-08 16:33:23.317903, Thread-3 : début du calcul de l'impôt des 4 contribuables
2020-08-08 16:33:23.317903, Thread-4 : début du calcul de l'impôt des 1 contribuables
2020-08-08 16:33:23.379221, Thread-2 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"], "csrf_token": "ImFiZmZkYjZmMzFkZDc2YWRjNWYwOGM0NTBmMGM4ODJjYzViOWI4NGEi.Xy63sw.H5L0--yWsvfaWvggrGw78z5VnN0"}
2020-08-08 16:33:23.381073, Thread-4 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"], "csrf_token": "ImY5YzQyMjlkYzcyYmM4YmZiMGI0NWY5MjE4MzIzNDExZjc0MGQ3MWQi.Xy63sw.q6olg7IP_g2ro_RBFRCX1BX90g8"}
2020-08-08 16:33:23.386982, Thread-3 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"], "csrf_token": "IjkxZGNlN2YyMmUxMjQ0M2Y0MTdjNDQ4ZmQ1MDMxZjkwNjBhNzAzZjMi.Xy63sw.-6buL11No3UJBlElpW4tX4B-lp0"}
2020-08-08 16:33:23.390269, Thread-1 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"], "csrf_token": "IjIxNmU4MDQyZDFmZmIyZDlmZjE4MzNlNDUzYzFjMGYxMWYxYzEwNGYi.Xy63sw.fgs6Cm2owsJf4NjTm7gKrVESabI"}
2020-08-08 16:33:23.413206, Thread-2 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie", "csrf_token": "ImFiZmZkYjZmMzFkZDc2YWRjNWYwOGM0NTBmMGM4ODJjYzViOWI4NGEi.Xy63sw.H5L0--yWsvfaWvggrGw78z5VnN0"}
2020-08-08 16:33:23.422877, Thread-2 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 1}, {"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0, "id": 2}], "csrf_token": "ImFiZmZkYjZmMzFkZDc2YWRjNWYwOGM0NTBmMGM4ODJjYzViOWI4NGEi.Xy63sw.H5L0--yWsvfaWvggrGw78z5VnN0"}
2020-08-08 16:33:23.428622, Thread-4 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie", "csrf_token": "ImY5YzQyMjlkYzcyYmM4YmZiMGI0NWY5MjE4MzIzNDExZjc0MGQ3MWQi.Xy63sw.q6olg7IP_g2ro_RBFRCX1BX90g8"}
2020-08-08 16:33:23.429127, Thread-3 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie", "csrf_token": "IjkxZGNlN2YyMmUxMjQ0M2Y0MTdjNDQ4ZmQ1MDMxZjkwNjBhNzAzZjMi.Xy63sw.-6buL11No3UJBlElpW4tX4B-lp0"}
2020-08-08 16:33:23.429127, Thread-1 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie", "csrf_token": "IjIxNmU4MDQyZDFmZmIyZDlmZjE4MzNlNDUzYzFjMGYxMWYxYzEwNGYi.Xy63sw.fgs6Cm2owsJf4NjTm7gKrVESabI"}
2020-08-08 16:33:23.429127, Thread-2 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée", "csrf_token": "IjU1YjlmZDA0OWRhNTJlODFmYjgyYjlhM2ExYWNhZmUzNTk2NjA5NGIi.Xy63sw.nyNSvkcG6iG0oIMBjtYPo8ySgdw"}
2020-08-08 16:33:23.438519, Thread-2 : fin du calcul de l'impôt des 2 contribuables
2020-08-08 16:33:23.443033, Thread-4 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 1}], "csrf_token": "ImY5YzQyMjlkYzcyYmM4YmZiMGI0NWY5MjE4MzIzNDExZjc0MGQ3MWQi.Xy63sw.q6olg7IP_g2ro_RBFRCX1BX90g8"}
2020-08-08 16:33:23.446510, Thread-3 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0, "id": 1}, {"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 2}, {"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0, "id": 3}, {"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0, "id": 4}], "csrf_token": "IjkxZGNlN2YyMmUxMjQ0M2Y0MTdjNDQ4ZmQ1MDMxZjkwNjBhNzAzZjMi.Xy63sw.-6buL11No3UJBlElpW4tX4B-lp0"}
2020-08-08 16:33:23.453477, Thread-1 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0, "id": 1}, {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347, "id": 2}, {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0, "id": 3}, {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 4}], "csrf_token": "IjIxNmU4MDQyZDFmZmIyZDlmZjE4MzNlNDUzYzFjMGYxMWYxYzEwNGYi.Xy63sw.fgs6Cm2owsJf4NjTm7gKrVESabI"}
2020-08-08 16:33:23.457912, Thread-4 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée", "csrf_token": "IjQ0ZDQxODgzN2M5NjRiYWI0NjA2MTk5YWFkNGFhMzY1M2IxNWMyNDIi.Xy63sw.mOa5MKXvJ-EXf_qEok-OqC5j_mg"}
2020-08-08 16:33:23.458442, Thread-4 : fin du calcul de l'impôt des 1 contribuables
2020-08-08 16:33:23.459045, Thread-3 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée", "csrf_token": "ImQ0NDZlYmViYjY1ZDUxYzJhMTNmM2JiZTRkMjBjZGJkYzE0OGVkYzMi.Xy63sw.fviTJz4zFDqVLlVlkrosT_JRPww"}
2020-08-08 16:33:23.459700, Thread-3 : fin du calcul de l'impôt des 4 contribuables
2020-08-08 16:33:23.460492, Thread-1 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée", "csrf_token": "Ijg3MjQ1NGUyYTUyOGEyNTdmZmNmYWZkMmU2OTgyMzUwNjI1YTlhZjIi.Xy63sw.I0xBl9Q8DzsuXPSgOdeARc_VKBA"}
2020-08-08 16:33:23.460492, Thread-1 : fin du calcul de l'impôt des 4 contribuables
2020-08-08 16:33:23.460492, MainThread : fin du calcul de l'impôt des contribuables

Arka arkaya alınan CSRF jetonlarına bakıldığında, hepsinin farklı olduğu görülür.