PHP unserialize() grėsmės
Tomas Lažauninkas, 2009-12-21 15:43:30

Atrodo, kad PHP jau laužė, kas tik netingėjo, todėl, panašu, PHP saugumo situacija po truputį gerėja. Jau nebeaptinkama tiek daug SQL injekcijos pažeidžiamumų ar nutolusio failo įterpimo, lokalaus failų skaitymo ar vartotojų duomenų perdavimo eval() funkcijai pažeidžiamumų. Tačiau situacija IT saugumo pasaulyje nestovi vietoje, tad vis atrandama naujų atakos krypčių. Visai neseniai vienas iš turbūt labiausiai PHP kūrėjams saugumą gerinti priverčiantis IT saugumo tyrinėtojas Stefan Esser pristatė prezentaciją su keliais PHP atakų vektoriais.

Vienas iš jų yra unserialize() funkcijos išnaudojimas apdorojant vartotojo perduodamus duomenis. PHP funkcija unserialize() naudojama atkurti PHP reikšmę iš „serializuoto“ pavidalo. Funkcija palaiko įvairių tipų duomenis: eilutes (strings), skaitines reikšmes, masyvus ir, kas svarbiausia nagrinėjant šią temą, PHP objektus. Dar vienas dalykas, kurį reiktų pabrėžti, tai PHP taip vadinamieji magiškieji metodai, kurie gali būti naudojami klasėse. Šie metodai yra magiški tuo, kad jie vykdomi automat(g)iškai  įvykus tam tikroms situacijoms. Metodai __wakeup() ir __destruct() bus vykdomi automatiškai (jei yra aprašyti) vykdant funkciją unserialize().  Pavyzdžiui įvykdžius šį  kodą bus įvykdytas __destruct() metodas:

<?php
class traskutis {
     function __destruct() {
          echo 'vykdoma __destruct funkcija';
      }
}
unserialize('O:9:"traskutis":0:{}');
?>

Po truputį ryškėja galimos atakos kontūrai. Iš esmės unserialize() funkcija nėra pavojinga, jei jai nėra perduodami vartotojo kontroliuojami duomenys, tačiau leidžiant vartotojui perduoti jo kontroliuojamus duomenis, iškylą saugumo problemos, kadangi pakišdamas reikiamus duomenis vartotojas gali keisti objekto, kuris bus naudojamas automatiškai vykdant __wakeup() ir __destruct() metodus,  parametrus pagal savo pageidavimus. Sukurkime butaforinį niekur nepritaikoma pavyzdį.

<?php

class data{
    var $data;
    var $dir;

    function Data($data){
        $this->data=$data;
        $this->dir='';
    }

    function rasyt(){
        if(!$fp=fopen($this->dir.'failas.php','w')){
            trigger_error('Klaida atidarant faila',E_USER_ERROR);
        }
        fwrite($fp,$this->data);
        fclose($fp);
    }
   
    function skaityt(){
        if(!$contents=file_get_contents($this->dir.'failas.php')){
            trigger_error('Klaida skaitant faila',E_USER_ERROR);
        }
        return $contents;
    }


    function __destruct() {
 $this->rasyt();
 }
}

unserialize('O:4:"data":2:{s:4:"data";s:16:"<? phpinfo(); ?>";s:3:"dir";s:0:"";}');
?>

Šitame pavyzdyje aprašyta paprasta PHP klasė, kuri turi dvi funkcijas: įrašyti į failą bei skaityti failą. Taip pat ji turi ir minėtą stebuklingąjį metodą __destruct().  Mes funkcijai unserialize() perduodam vartotojo kontroliuojamus duomenis (vietoj hardcodintos reikšmės galėtų būt GET, POST parametras).  Taigi, kviečiant unserialize() su „data“ objekto aprašyta reikšme, automatiškai iškviečiamas __destruct() metodas kuriame kviečiama   funkcija rasyt(). Kaip matome unserialize() funkcijai perduodamoje reikšmėje yra nurodyti ir objekto parametrai( s:4:"data";s:16:"<? phpinfo(); ?>"), parametras data, kurio reikšmė norimas PHP kodas. Tokiu būdu vien tik kreipdamiesi į PHP funkciją unserialize() galime susikurti objektą su norimas parametrais ir taip pakreipti scenarijaus vykdymą norime linkme, šiuo atveju į failą įrašyti norimą turinį . Unserialize() sukeliamas PHP kodo vykdymas būtų ekvivalentus tokiam kodui :

<?

<...> // praleidžiam aukščiau aprašytą klasę

$x = new Data("<? phpinfo(); ?>");
$x->rasyt();

?>

Aišku tai yra išgalvotas scenarijus, bet jis puikiai atspindi šio pažeidžiamumo esmę. Realiame gyvenime vargu ar pavyks atrasti taip paprastai išnaudojamų šio tipo pažeidžiamumų, kadangi iš pradžių reikia rasti objektus su su __wakeup() ar __destruct() metodais ir, kad juose vykdomą kodą eitų pakreipti norima linkme keičiant tam tikrus objekto parametrus. Tas pats Stefan Esser pademonstravo praktinius šio pažeidžiamumo pavyzdžius Zend framework paremtuose aplikacijose Piwik ir PHPIDS. Šie pažeidžiamumai ir jų išnaudojimas yra gan sudėtingi, reikia aprašyti net kelis PHP objektus su specialiomis reikšmėmis. Norintieji galės išnagrinėti šių pažeidžiamumų išnaudojimą pateiktose nuorodose. O  aš šiuo straipsniu norėjau parodyti naują atakos kryptį PHP aplikacijose iš kurios išplaukia dar viena taisyklė PHP programuotojams: niekada neperduoti vartotojo kontroliuojamų duomenų funkcijai unserialize()!


Komentarai

Vardas:
Komentaras:

Copyright © 2005 - 2010, UAB „Critical Security“