PHP - Protejarea aplicatiilor impotriva CSRF

Post Title

PHP - Protejarea aplicatiilor impotriva CSRF

In acest tutorial vom invata despre CSRF ( Cross Site Request Forgery ) si despre cum putem proteja aplicatiile create impotriva acestei probleme de securitate.

In primul rand, CSRF prespune ca un utilizator rau intentionat sa trimita cereri catre o aplicatie in numele unui alt utilizator pentru a putea avea acces la datele victimei.

Un scenariu fictiv dar plauzibil

Sa presupunem ca avem cont si suntem autentificati pe site-ul http://nume.com Pe acest site, ne putem modifica datele de autentificare la pagina http://nume.com/update_account.php La aceasta pagina se afla un formular cu urmatorele campuri.

<h1>Update your account</h1>
<hr>
<form action="update_account.php" method="POST">
    <p>Username: <input type="text" name="username"></p>
    <p>Email: <input type="email" name="email"></p>
    <p>Password: <input type="password" name="password"></p>
    <button type="submit">Update Account</button>
</form>

Dupa cum putem observa, atunci cand butonul este apasat, se trimite o cerere HTTP folosind methoda POST catre resursa ( fisierul ) update_account.php impreuna cu datele din campurile <input>.

Sa prespunem ca pe acest site se afla un forum, iar pe forum, oamenii posteaza diferite subiecte cu link-uri catre resurse externe.Unul sau chiar mai multe link-uri pot duce catre un site creat special pentru a pune in aplicare CSRF.

http://fake.com

Fiind victima, noi vom accesa in mod liber unul din aceste link-uri.Pe site-ul pregatit pentru CSRF se afla un element html <iframe>, ascuns.Acest element HTML incarca fisierul secret.html ce contine chiar formularul de mai sus.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
    <h1>Hahahaha...</h1>
    <iframe src="secret.html" frameborder="0" style="display: none;"></iframe>
</body>
</html>

secret.html

<html>
<head></head>
<body onload="document.getElementById('form').submit();">
    <form id="form" action="update_account.php" method="POST">
    <p>Username: <input type="text" name="username" value="Catalin"></p>
    <p>Email: <input type="email" name="email" value="[email protected]"></p>
    <p>Password: <input type="password" name="password" value="secret"></p>
    <button type="submit>Update Account</button>
</form>
</body>
</html>

Dupa cum putem observa, in momentul in care acest iframe ascuns se incarca, folosind putin Javacscript, formularul este trimis chiar catre adresa site-ului unde noi suntem autentificati, catre resursa update_account.php.Mai mult decat atat, fiecare camp <input> foloseste atributul value pentru a specifica valorile dorite de utilizatorul rau intentionat.

Pentru ca suntem autentificati pe http://nume.com, cererea va fi luata in calcul, astfel, datele contului au fost schimbate, contul fiind compromis.

De retinut, ca acest lucru sa se intample, noi trebuie sa accesam site-ul folosit ca paravan.Accesand site-ul, in mod implicit si ascuns, datele contului sunt modificate...

Site-ul http://nume.com, nefiind protejat, nu are o metoda sa-si dea seama cine este cel care modifica datele contului

Cererea de modificare a contului, din partea utilizatorul intentionat, este luata in calcul datorita faptului ca noi suntem autentificati pe http://nume.com

Alte scenarii

Evident, mai exista si alte scenarii.Suntem autentificati tot pe aceleasi site, http://nume.com. La adresa http://nume.com/logout.php ne putem deconecta de pe contul nostru.

Procedand ca in scenariul precedent, vom accesa site-ul folosit ca paravan pentru CSRF, dar de aceasta data, in pagina acasa a site-ului se gaseste un element HTML <img>.

<img src="http://nume.com/logout.php"/>

In mod implicit, vom fi deconectati de pe contul nostru.Acest lucru se intampla deoarece elementul <img> folosind atributul src trimite o cerere HTTP folosind metoda GET catre adresa URL http://nume.com/logout.php.

De asemenea, un alt scenariu apare atunci cand aplicatia nu este protejata impotriva XSS sau Cross Site Scripting.Acesta bresa ofera utilizatorului rau intentionat posibilitea de a injecta cod ( HTML, Javascript etc ) in aplicatiile noastre cu scopul de a altera functionarea lor.Intr-un astfel de scenariu, utilizatorul rau intentionat va incerca sa "fure" id-ul sesiunii curente a victimei.Astfel, va putea trimite cereri catre aplicatia noastra in numele victimei.

Cum ne putem proteja aplicatiile create?

In primul rand, in ceea ce priveste XSS, trebuie sa folosim metode de a converti caracterele speciale ( <, >, ', ", & ) din datele primite de la utilizatori, in entitati HTML, in cazul in care dorim sa afisam aceste date.In PHP, avem functia htmlspecialchars() care face acest lucru.Mai mult decat atat, trebuie sa protejam si sesiunea curenta prin regenerarea ID-ului la fiecare cerere HTTP.

In al doilea rand, cand vorbim despre lucru cu formulare, avem nevoie de o solutie prin care putem stii ca cel care trimite cereri catre aplicatia noastra, este chiar utilizatorul autentificat.

Pentru a putea face acest lucru, vom atasa fiecarui formular un <input> ascuns ce contine un token ( un sir de caractere la intamplare ) stocat in sesiunea curenta.Astfel, cand se trimit cereri HTTP din formulare, vom verifica daca token-ul trimis este identic cu cel stocat in sesiunea curenta.

Avem doua cazuri:

  1. Token-ul trimis din formular nu este identic cu cel din sesiune, astfel, cererea nu are cum sa fie trimisa de utilizatorul autentificat, cineva incerca sa trimita o cerere in numele acestui utilizator.
  2. Token-ul trimis din formular este identic cu cel din sesiune, totul este bine, vom regenera token-ul din sesiune pentru mai multa securitate.

Sa nu uitam, sesiunea creata este valabila doar pentru utilizatorul autentificat, ceea ce inseamna ca acel formular incarcat de elementul <iframe> pe site-ul http://fake.com nu mai poate functiona, dat fiind faptul ca nu are de unde sa stie ce valoare are token-ul creat in sesiunea utilizatorului autentificat.

Sa punem in practica ceea ce am discutat mai sus!

Vom incepe prin crearea unei clase pe care o vom folosi.

CSRF.php

<?php

class CSRF
{
    /**
     * Metoda statica pentru generare token
     */
    public static function generateToken()
    {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(30));

    } 

    /**
     * Metoda statica pentru returnarea token generat
     */
    public static function getToken()
    {
        if (!isset($_SESSION['csrf_token'])) {
            self::generateToken();
        }

        return $_SESSION['csrf_token'];
    }

    /**
     * Metoda statica pentru verificare token
     */
    public static function verify($token) 
    {
        if ($token != self::getToken()) {
             return false;
        }

        return true;
    }
}

update_account.php

<?php 
session_start();

// regeneram ID-ul sesiunii curente
session_regenerate_id(true);

// includem clasa creata
require_once 'CSRF.php';

//TODO: verficam daca utilizatorul este autentificat

// o cerere folosind metoda POST a fost trimisa
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // verirficam autenticitatea token-ului
    if (!isset($_POST['csrf_token']) || !CSRF::verify($_POST['csrf_token'])) {
        // cineva incerca CSRF
        // oprim executia script-ului
        die('We have a problem!');
    } 

    // totul este bine, regeneram token-ul din sesiune
    CSRF::generateToken();

    //..
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
    <h1>Update your account</h1>
    <hr>
    <form action="update_account.php" method="POST">
        <p>Username: <input type="text" name="username"></p>
        <p>Email: <input type="email" name="email"></p>
        <p>Password: <input type="password" name="password"></p>
        <input type="hidden" name="csrf_token" value="<?php echo CSRF::getToken();?>">
        <button type="submit">Update Account</button>
    </form>
</body>
</html>

Trebuie sa ne protejam toate formularele folosind o astfel de metoda.Evident, aceasta este un exemplu cat mai simplu ca voi sa intelegeti despre ce este vorba.

In ceea ce priveste protejarea impotriva deconectarii de pe un site fara acordul vostru, trebuie sa folositi o metoda similara cu cea de sus, unde am folosit acel token pentru a stii cine trimite cereri catre aplicatie.Folosind Javascript, atunci cand se apasa optiunea de Deconectare, se trimite o cerere folosind metoda POST catre fisierul logout.php.In cererea trimisa se include si token-ul generat.In fisierul logout.php facem aceleasi verificari ca mai sus.

Ei bine, cam atat cu acest tuturial, sper sa va fie de folos!

Autor articol
David: Why do you want to leave me? Why? I'm sorry I'm not real! If you let me, I'll be so real for you!

Comentarii

Comentariu adaugat de Bucur
Multumesc foarte mult pentru articol, chiar aveam nevoie pentru un mic CMS in php.
go to page top Bucur | 2018-07-28

  • 1
Trebuie sa fii logat sa poti lasa un comentariu Autentificare Inregistrare Logare cu Facebook
top