Pahavara: üks huvitav veebiproksi

Käes on Locked Shields küberõppuse nädal … aga kui sa ei ole LSil, siis on sul kindlasti aega tegeleda päris pahavaraga.

Puhastasin nädalavahetusel järjekordset veebi, kuhu oli sisse pääsetud nõrga admin-kasutaja salasõnaga. Kuna rünne oli toimunud hiljuti, oli mugav võtta ette varukoopia ning sellega võrreldes tuvastada muutunud failid. Ilmnes, et katalooge mööda oli laiali puistatud 35 erinevat tagaust või pahavaralise funktsionaalsusega faili.

Et küberkurjategijate tööd veidi laiemal tutvustada panin välja auhinnad… ja otsustasin koodi Facebookis huvilistele lammutada anda. See blogipost ongi kokku pandud osalt Zone FB-lehe postituse ja Vabakutseliste gruppi jagatu kommentaarides olevatest lahendustest – ning osalt Zone maja-sisestest tähelepanekutest.

Kui tahad enne lahenduse lugemist (või selle kõrvale) koodi vaadata, siis:

Niisiis saab kõik alguse sogastatud koodist:

Kristjan muutis (kasutatud) funktsioonide ja muutujate nimed arusaadavaks ja lisas kommentaarid – saamaks aru, mida koodijupp teeb ja kas seda on üldse ohutu oma arvutis või serveris käivitada:

Kuna muutujas $payLoad olev laeng on URL-kodeeringus mida pahavara-tuvastajatel lihtne läbi näha, on kasutataud täiendavat teisendustabelit selle sogastamiseks:

Olgu lisatud, et algses koodis näeb see eval ehk stringis olevat PHP koodi käivitav funktsioon välja alljärgnev, kurja eval() peitmiseks lihtsamate otsingute eest on funktsiooni nimi eraldi real ning lisaks üks kommentaar:

Kui asendada funktsion eval() näiteks print()’iga saame väljundiks laengu koodi – aga mulle meeldib rohkem tulemus faili kirjutada:

file_put_contents ( '_payload.php', decodePayload( $payLoad, $characterMap ) );

Ja saamegi asuda laengut uurima. Väga levinud tava on keelata ära veateadete väljastamine, aga siin on igaks juhuks võetud maha ka skripti täitmise ajapiirang (mis sisuliselt võimaldab seda kasutada ka teenustõkestusründeks andes kõigile lubatud PHP-protsessidele midagi aegavõtvat teha):

Edasi, otsime päringust maagilist küpsist – AGA Kristjani kommentaar on väikse näpukaga, nimelt väljutakse skriptist kui leitakse mõni küpsis mille väärtus EI VASTA soovitud nimele (küpsise nimi pole seejuures oluline).

Lisaks on koodis aga üks väga tore loogikaviga mille avastas Kurt Moser meie arendustiimist, vaata kas märkad:

Nojah, seda tsüklit ei täideta ja skriptist väljumist ei toimu, kui päringuga pole kaasas ühtki küpsist. Ehk sisuliselt on kogu piirang mõttetu – funktsionaalsele osale saab ligi igaüks.

Edasi läheb huvitavamaks:

Selle jupi juures tasub kindlasti “kastist välja” mõelda – php://input on tavapäraselt kasutusel POST päringute puhul, mitte miski ei keela selle kasutamist ka GET päringus. Sellisel kujul GETi kasutamise võlu seisneb aga selles, et veebiserverid päringu keha harilikult ei logi. Aga olgu siinkohal ka tõestus Elar Langilt:

<?php
var_dump(file_get_contents('php://input'));
?>

$ curl -X GET -s --data "töötab ju" http://localhost/z-server.php
string(11) "töötab ju"

$ curl -X POST -s --data "töötab ju" http://localhost/z-server.php
string(11) "töötab ju"

Järgmise jupi võtan ma aga oma koodiredaktorist, sest see oskab vigu tuvastada:

Jahaa – funktsioon split() kuulutati iganenuks juba PHP versioonis 5.3 ja alates 7.0 on see eemaldatud (tx veelkord Kurtile!). Ehk PHP uuemate versioonide puhul see kood ei tööta ja annab viga – võinoh… kuna vigade väljastamine on ülalpool ära keelatud, siis on tulemuseks Error 500, nagu ilmselt avastas ka ründaja:

94.130.35.51 - - [27/Mar/2019:10:02:21 +0200] "GET /wp-admin/css/colors/sunrise/lngzdhkh.php HTTP/1.1" 500 3527 "" "Mozilla/5.0 (Linux; Android 7.0; SAMSUNG SM-G935F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/5.4 Chrome/51.0.2704.106 Mobile Safari/537.36" (F9F8DACB-0.001)

Pannes split() asemele explode() saab sellest murest kergesti üle.

decrypt() on sisuliselt XOR krüpteering ehk sama võtmega saab lahti ja kinni – nagu Karl-Sander Erss FBs kommentaaris demos:

> var_dump(decrypt(decrypt('asd'))==='asd');
bool(true)

Kuivõrd võtmeks on $_SERVER[‘HTTP_HOST’]  ja $_SERVER[‘REQUEST_URI’] on ka see kergesti ära-arvatav ehk nagu eelnevalt öeldud saab sellele koodi ära kasutada kesiganes tema olemasolu teab. Nii küpsis kui krüpto on täiesti mõttetud 🙂

Aga … mida see send_data1() siis teeb? Võtab saadud parameetrid (näidisandmed ülalolevas ekraanilaksus) ja teeb nendega HTTP POST või GET-päringu… ja tagastab tulemuse ehk tegu on veebiproksiga:

function send_data1( $data ) {
   $head = "";
   foreach ( $data["headers"] as $key => $value ) {
      $head .= $key . ": " . $value . "\r\n";
   }
   $params = array(
      'http' => array(
         'method'  => $data["method"],
         'header'  => $head,
         'content' => $data["body"],
         'timeout' => $data["timeout"],
      )
   );
   $ctx = stream_context_create( $params );
   $result = @file_get_contents( $data["url"], false, $ctx );
   if ( $http_response_header ) {
      if ( strpos( $http_response_header[0], "200" ) === false ) {
         $result = "HTTP_ERROR\t" . $http_response_header[0];
      }
   } else {
      $result = "CONNECTION_ERROR";
   }
   return $result;
}

Tuleb tunnistada, et file_get_contents() kasutamine koos kontekstiga HTTP-päringu parameetrite määramiseks pole minule kunagi varem ette jäänud, aga väga huvitav meetod. Lisaks: kuna paika pannakse ka timeout ning alguses on skripti täitmise ajapiirang maha võetud… siis võib see väääääga kaua aega võtta ehk olla kasutatav DOS ründeks.

Huvitavat jagub aga veelgi: nimelt EI MÄÄRATA ära HTTP protokolli versiooni ning vaikimisi on selleks HTTP/1.0 – ning minu oletuse kohaselt kasutatakse ülevõetud servereid järgmiste serverite ründamiseks. Hmm, vaatame korraks rünnatud saidi logi, filtreerides õnnestunud HTTP/1.0 päringuid:

Ja tõepoolest, need IPd pärinevad erinevatest pilve- või veebimajutusteenustest. Võib oletada, et ründajatel on oma veebiproksi, mis kasutab ülevõetud serverites olevaid veebiproksisid. See muudab võimatuks lihtsad lahendused nagu IP-põhised piirangud – mh kaitse WordPressi wp-login.php vastu suunatud paroolide jõurünnete tõrjumise “lubame vaid kolm katset samalt IPlt” meetodil.

Tänud kõigile kaasa mõtlemast, loodetavasti saime koos targemaks 🙂