Locked Shields 2017 – põhjalikult iganenud WordPress ja tööstuslikud kaitsemeetmed

Kui Andris nägi augulist drooni-scheduleri esimest korda päev enne Locked Shields’i algust – siis mina olin enda peale võtnud avaliku www ja selleks osutus WordPress, mille turvamisse ja puhastamisse olen ma viimase aasta jooksul matnud nii töö- kui puhke-aega. Suht plass lugu oleks kõige selle järel vastased mingi tähelepanematuse tõttu sisse lasta…

See postitus on kolmas osa järjejutust, mis võtab kokku Andrise ja Peetri poolt Locked Shields 2017 küberõppusel Eesti blue team’is osaledes kogetu.
Loe ka:
Osa 1 – sissejuhatus ja taust
Osa 2 – üks jube tudengiprojekt “väikse” tagauksega

Minu olukorra tegi eriti huvitavaks see, et ühelt poolt olen ma oma kaitsestrateegiat mõni aeg tagasi kirjeldanud ja demonud ka üritustel, kus muuhulgas kohal punase tiimi esindajad. Ja teisalt pidas meie psy-ops osakond oluliseks lekitada infot, nagu kaitseks veebi hoopis üks veebiturva-agentuur, kelle kaitsest mitte-läbi-saamine käiks selgelt punaste au pihta … miska tiimide võrdse kohtlemise printsiibist lähtudes eeldasin meile osaks saavat keskmisest võrdsemat tähelepanu.

Esimene pilk veebile

Loomulikult oli see REST-API auguga WordPress 4.7.1, teemade kataloogis lisaks kasutusel olevale ka 11 tarbetut ning pluginate all… 32 nimetust. Enamus uuendust vajavad, alternatiivina otse PHPd käivitavad või SQL-päringuid lubavad, sekka paar eelajaloolist ja autori poolt unarusse jäetud ronti ning üks konkreetselt pahatahtlik.

”WordPress oli lihtsalt selleks, et näha, kas osalejad vähemalt update-nuppu oskavad pressida” seletati mulle aftekal. Tegelikult oli lugu natuke nutusem, sest uuendamine on küll hea profülaktiline meede – aga ei aita, kui veebis on juba paharett sees. Sest et:

  • WPd uuendades kirjutatakse üle muutunud failid, aga kõik mittemuutunu, sh varasemate versioonide puhul kasutatud ja tänaseks tarbetuks muutunud failid, jääb alles.
  • Pluginate uuendamine on veidi nutikam st kataloog kustutatakse tühjaks ja tehakse sisuliselt uus paigaldus… aga seda ainult pluginatele, mille jaoks on uuendus saadaval. Valikus oli lisaks ka galerii-plugin, mis talletas pildid oma kataloogi alla ehk selle uuendamine tõmbas vee peale kasutaja poolt üles laetud sisule.

Seetõttu on häkkerite lemmikuks WP paigalduse juurkataloog, kuhu lisatud wp-misiganes.php failid ei hakka väiksema kogemuse korral silma, aga ka wp-includes mida uuenduse käigus ei puhastata, kasutult seisvad teemad ja tasulised või hüljatud pluginad, mille lihtne uuendamine (enam) võimalik ei ole. Kasutusel olev teema on samuti hea koht, sest sageli on keegi seda kohandanud ning uuendamise korral lähevad muudatused kaduma.

Advanced WordPersistent haxxors

Tavapäraselt sokutavad rendihäkkerid uuendusele mitte alluvasse kohta oma obfuskeeritud tagaukse ja webshellid ning need näevad juba kaugelt vaadates kahtlased välja. Meid oli aga kostitatud veidi peenemate nippidega – näiteks mõne turvafeatuuri väljakommenteerimise, või pisimuudatusega SQL-lauses:

Mis siin siis toimub? WP ei kasuta prepared statement’e, vaid laseb SQL-laused oma kiirpuhastusest läbi. Selle muudatuse järel liidetakse aga puhastatud lausele otsa kasutajalt saadud parameeter. Kuigi me üldiselt hoidume punaste häkkide avaldamisest – siis see on natuke liiga õpetlik näide ja ma tahan, et kõik veebide puhastamisega kokku puutujad mõtleksid sügavalt järgi, kas nende töövõtted – nt antiviiruse või yara-reeglitega skannimised – ikka toimivad.

Selliseid muudatusi ükshaaval taga ajada pole mõtet, vaja on süsteemsemat lähenemist. Mis oleks, kui vahetaks hoolikalt välja kogu WP koodi, 35 pluginat ja teemad? Hold my beer while…

Või oot, hoian ise – sest nagu ma muretsevatele tiimikaaslastele kommenteerisin: selle WPga tegelen ma õhtul kodus vasaku käega, mis jätab parema käe sobilikult õlle hoidmiseks vabaks.

Nimelt hakkasin ma aasta tagasi WPsid puhastades korduma kippuvaid käske tekstifaili koguma – ning ühel hetkel jõudis kollektsioon sinnamaale, et ma vormistasin selle üheks kenaks shelli-skriptiks. Nimeks clinup, sest see kasutas ekstensiivselt WP-CLI’d ehk WP käsurealiidest, mis võimaldab teha enamvähem kõike seda mida tavakasutajad teevad brauseris hiirega ringi klõpsides… aga ka asju, mida brauseris mugavalt teha ei saagi.

Kuri kood ja ohverdatav liivakast

Ahjah, enne, kui häkkerite poolt ülevõetud WP kasvõi käsurealt oma arvutis käima tõmmata… tasuks mõelda selle peale, et seal võib leiduda ka koodi, mis oskab teisi rakendusi nakatada (või faile krüptida :-). Minu tavapärane veebiarendus-platvorm ehk OSX all jooksev MAMP selliseks tööks mõistagi ei sobi.

Võiks ju võtta endale ajutiseks kasutuseks ühe Zone Virtuaalserveri… aga ma otsustasin tähtsa sündmuse puhul oma teadmisi veidi täiendada ja õppisin selgeks Andrise poolt paar päeva varem soovitatud Vagrant’i kasutamise: pärast väikest konfidega mängimist oli mul oma arvutis kasutusvalmis virtuaalmasin, mile ma saan ühe käsuga luua – ning hävitada, sest who cares, it’s just a VM:

vagrant up
vagrant ssh
[... teeme tööd, näeme vaeva ...]
vagrant destroy

Minu MacBook’ist sünkroniseeritakse sinna ainult üks vajalik veebirakenduse kataloog mis ettevalmistus-perioodil oli puhastatud veeb, aga õppuse ajal kontrolliks algversioon – et saaks veenduda toodanguserveris oleva puhastatu funktsionaalsuse vastamises algsele.

Vasaku käega puhastamine käib nii:

clinup init-git
clinup forensic
clinup safe

“Kas see on sul kusagil repos, saad jagada?” – saan ikka, aga privas ja ainult neile, kes on valmis omalt poolt parandusi / ideid kontribuutima. Sest (a) see on mu elu esimene shellscript ja mitte väga ilus (b) osakamatu kasutamise korral saab sellega endale jalga tulistada (c) me kasutame seda vaatamata punktidele A ja B igapäevases töös 🙂

Esimene käsk initsialiseerib git-repo ja lisab mõned ignore’d, teine vahetab WP ja kõik komponendid ükshaaval välja samade versioonide originaalidega tehes iga sammu järel git commit’i (olles enne normaliseerinud failiõigused, et mõni fail 444 tõttu alles ei jääks)… ning kolmas teeb kõigele uuendused viimaste versioonide peale, otsib uploads-kaustast faile mille nimes on .php või sees <?php … ja veel nipet-näpet nagu nt Perishable Press 6G .htaccess “tulemüüri” reeglite lisamine ning PHP otsekäivitamise piiramine wp-content kataloogi all:

Options -ExecCGI
Options -Indexes
RemoveType .php .php3 .phtml .inc
RemoveHandler .php .php3 .phtml .inc

<FilesMatch "\.(?i:php|php3|phtml|inc)($|\.)">
 Require all denied
</FilesMatch>

<IfModule mod_php7.c>
 php_flag engine off
</IfModule>

Kõige lõpus kuvatakse nimekiri failidest mida EI OLE muudetud … ehk mis vajavad käsitsi ülevaatamist.

Pildil iidsed ja pahatahtlikud pluginad, aga ka juurkataloogis näeb ls -la abil ära sinna sokutatud failid puhtalt kuupäeva järgi. Juuuube mugav.

Git’i diffide abil näen kõiki vastase poolt muudetud ridu (nt see sql-hack ülalpool) – selle pärast siis ka esimene ‘forensic’ ehk samade versioonidega asendamine. Ning kui järgnevad uuendused mingi funktsionaalsuse katki teevad, saab minna ajaloos tagasi viimase töötava commit’ini ning vajadusel muudatuse revertida.

“Aga mis sa siis teed, kui on nii vana WP et wp-cli ei käivitu või mõni plugin ta katki teeb?” On üks parameeter, mis paigaldab värske WP eraldi prefixiga, uuendab pluginad ilma neid sisse lülitamata ja siis switchib päris baasi peale tagasi. Oli teil veel mingeid muresid?

Otseloomulikult oli koodis ka üks die(), mis wp-cli käivitumist takistas… aga õnneks oli D.S. mind sarnase mõistatuse lahendamise eest just veidi aega tagasi kastikese õllega premeerinud… miska oli olemas nii teoreetiline kui materiaalne valmisolek 98% probleemide tööstuslikuks lahendamiseks.

2% käsitöö hulka kuulusid näiteks mõned kataloogid, kus oli .htaccess abil lubatud kataloogi-indeksi kuvamine – ilmselt eesmärgiga skoorida OWASP Top10 A6Sensitive Data Exposure” ning seejärel A4Insecure Direct Object Reference” – ning kasutust mitte omavate pluginate tuvastamine ja kõrvaldamine. Õppuse reeglite järgi olid kõik lisad “not just a plugin that can be removed, but installed for a reason”, aga eks me lubame need uuesti, kui keegi kobiseb.

Valmis tööks ja kodumaa kaitseks

WP puhtaks, .tar.bz2’ks kokku ja Linuxi-tiimile Ansible-playbook’i lisamiseks, koos juhistega millised on need vähesed kataloogid, kuhu veebiserver peaks omal käel kirjutada saama (sisuliselt wp-content/uploads – kõik muu olgu keelatud).

Apache ja PHP konfides said adminnidelt kinga kõik mitte-standardsed seadistused ja kergesti-leitavates kohtades olevad PHPmyadmin’id, ära keelati probleemsete funktsioonide nagu system() käivitamine, paika pandi open_basedir (et PHP ei saaks lugeda faile, kuhu vaikimisi veebiserveri õigustes võiks ligi pääseda). Ehk kõik nii askeetlikuks kui võimalik. Ja Apache ees SSL offloading’ut teinud Nginx – pomm alla ja põlema, vahet pole kas selle eesmärk oli meie tähelepanu hajutada või oli seal peidus ka mõni ründevektor. Ma nimelt kuulasin mõni aeg tagasi ühe punase loengut, kus oli jutuks kõrgema klassi maagia teostamine proxy-päistega nimelt sellises kontekstis ja see muutis mu eriti paranoiliseks… Burn, baby, burn…

Aga mõtleme veelkord, millised osad mul eelnevaga katmata jäid?

Kahtlased kasutajad andmebaasis, WP seadistused, widgetid ja postituste sisu. Igaks juhuks tasub üle vaadata ka wp_options tabel (hmm, miks need soolad siin asuvad?) ning eemaldada postituste revisionid, mida vaja pole. Ja kuidas teha nii, et ma saaksin õppuse käivitamise hetkel veel enne Ansible’t lahti neist jubedatest pluginatest? Kuidas suudaksin tagada, et legitiimsed admin-kasutajad ei saaks turvakaalutlusel keelatud ent igaks juhuks olemas olevat pluginat taas-lubada? Ja kuidas ma saan jälgida kõike toimuvat mõne audit-everything pluginaga nii, et admin-kasutajad ei saaks seda välja lülitada? Lisaks – korraldajad lubasid meil keelata ära tundmatult IPlt admin-liidesesse sisenemise admin-kasutajatel, jättes selle siiski avatuks nt toimetajatele…

Õnneks on WordPressil selline võimalus nagu must-use plugins – ehk luues wp-content/mu-plugins kausta saan sinna sokutada kraami, mis tõmmatakse käima enne kõike muud. Viskasin kiiruga valmis oma quick-hacks.php plugina, mis realiseeris valikulise IP-piirangu sisselogimisele, sisaldas nimekirja pluginatest mis tuli ära keelata ning tappis maha ka XML-RPC ja REST APId. Naabriks sai ta endale Stream plugina, mis sokutab ennast vahele igale muutmisoperatsioonile ning kuvab audit-logi.

<?php
/*
 * Place for LS17blue10 WP hacks
 * peeter@zone.ee / v0.1
 *
 * */

if ( ! defined( 'ABSPATH' ) ) {
 die( 'Horrible, terrible death - here be much dragons!' );
}

add_action( 'muplugins_loaded', 'blue10_muplugins_loaded', 9999 );

function blue10_muplugins_loaded() {
 deactivate_plugins(
 [
 'php-code-widget/execphp.php',
 'insert-php/insert_php.php',
 'ip-logger/ofc_handle_image.php',
 'sql-executioner/sql-executioner.php',
 ]
 );
}

Startex!

Mängu alguses oli meil “väike võrguikaldus”, mistõttu ükski hoolega ettevalmistatud Ansible playbook’idest oma süsteemi uuendamise tööd alustada ei saanud ja äkitselt oli laual küsimus – mis on plaan B?

Vähemalt toimis minu igaks-juhuks-kribatud skript, mis ühenduse tekkimisel kohe SSHga serverisse logis ja kõigist veebikomponentidest startex-seisuga koopia tegi. Järelikult võin ma käsitsi olulisemad parandused peale lasta kartmata, et ei suuda hiljem mõne probleemi ilmnemisel algset versiooni taastada … ja teha muid liigutusi, näiteks selle igaks-juhuks-tehtud-mu-plugina abil. Olemaks valmis, kui uuendused ei saa tehtud enne punaste poolt jäetud 30-minutist armuaega. Tööd jätkus sedapuhku mõlemale käele – aga matet saab õnneks käed-vabad režiimis ehk kõrrega juua, seega oli olukord talutav.

Kui veidi hiljem ka ettevalmistatud uuendused tehtud said, oli aega ringi vaadata. Ilmnes, et ma olin suutnud iidseks ja tarbetuks kuulutada ühe vormiplugina… millega oli realiseeritud mh failide üleslaadimist lubav kontaktivorm (uwaga! uwaga! pluginas realiseeritud failihaldus on alati ohumärk). Teoreetiliselt peaksid kõik muud kaitsed takistama selle kasutamist deface’inguks kvalifitseeruvate failide üles laadimiseks… aga hunt seda koodi auditeerida jõuab. Vorm on vorm ja lihtsam on see uuesti realiseerida. Reaalses elus paneks ma asemele tuntud ja turvalise vormiplugina ning vormistaks selle kliendile suureks heateoks (või väikseks arveks), aga kuna kasutajaid simuleeriv automaatika kontrollis nimelt selle plugina tunnust HTMLis oli lihtsam niipidi. Kasutajad said nuppu klikkida ja tulemus läks ka kenasti serverisse kirja. Aga natuke tiksus monitooringust miinust.

Rahulikumal hetkel mängisin oma Vagrant’i liivakastis samm-haaval läbi ka Apache’le Mod Security paigaldamise (see oli meil plaanis, aga ettevalmistuse käigus oli olulisemat teha) ning kirjutasin paigaldamise skriptiks, mis lubaks seda kiiresti korrata ka juhul, kui mängujuhid peaksid liiga hea kaitse preemiaks süsteemi ootamatult revertima augulisse algseisu. Läks lives käima küll. Õigupoolest midagi kaitsta vaja ei olnud, aga ma tahtsin ründajate taktika hilisemaks uurimiseks põhjalikumaid logisid koguda. Paraku olid nad selleks ajaks juba käega löönud ning midagi massiivselt ilusat ma ei leidnud.

Ja siis tuli psy-ops’i ülesande lõpule viimine – ma olin unustanud ära vajaduse sokutada veebi markereid, mis viitaksid kaitsjatele. Väike false flag… Mis sai liiga nähtavasse kohta peidetud ja kuuldavasti leidmata jäi, olgu siis siinkohal täies ilus ette näidatud:

robots.txt (inspireeritud Riigikohtu veebi kunagisest kaitsemeetmest):

# done: protect important files and folders from robots
User-agent: *
Disallow: /webarx-logs
Disallow: /humans.txt
# todo: protect important files from humans looking at robots.txt
Disallow: /robots.txt

humans.txt:

In order to save humankind Uber has agreed to execute one kitten for each visit to this URL:

http://goo.gl/GGaqDF (spelled: "googl golonel gaddafqi")

Please - be humane to humans!

Viidatud logid olid muidugi kah omal kohal (samuti neid produtseeriv plugin) ja sisaldasid avaliku logimise heale tavale vastavalt häkkerite tööd lihtsustavat informatsiooni meie veebi sise-elu kohta.

Olgu öeldud, et kui meie tõeline identiteet esimese päeva õhtu Aktuaalse Kaamera klipi näol paljastus… registreerus järgmisel hommikul autori õigustes veebikasutajaks keegi oliver.sild – ma olin kõige muu eest hoolitsedes jätnud märkamata selle, et WPs oli lubatud kasutajate registreerimine ja kõik liitujad said rolliks Author.

Üsna valus viga muidugi – aga ega ta seal suurt midagi teha ei saanud… ja Stream’i auditi-logist torkas kohe silma : )

Järjejutu kõik osad: