Kas tai per žvėris – deserialization pažeidžiamumas?
Programuotojai nuolat susiduria su poreikiu perduoti duomenis tarp skirtingų sistemų ar išsaugoti juos ilgesniam laikui. Čia į pagalbą ateina serializacija – procesas, kai objektus paverčiame į baitų seką, kurią galima išsaugoti faile ar persiųsti tinklu. Deserialization yra atvirkštinis procesas – kai tuos baitų srautus vėl paverčiame veikiančiais objektais programoje.
Problema kyla tada, kai programa aklai pasitiki duomenimis, kuriuos deserializuoja. Įsivaizduokite, kad jūsų aplikacija gauna užkoduotą objektą iš išorės šaltinio ir tiesiog jį „atgaivina” be jokių patikrinimų. Piktavalis gali suformuoti tokį objektą, kuris deserializacijos metu įvykdys kenkėjišką kodą jūsų serveryje. Tai kaip priimti paketą paštu ir jį atidaryti net nepažiūrėjus, kas siuntėjas.
Insecure deserialization yra viena iš tų spragų, kurios gali sukelti katastrofiškų pasekmių. OWASP Top 10 sąraše ji įtraukta ne veltui – šios spragos išnaudojimas gali leisti užpuolikui visiškai perimti serverio kontrolę, pavogti jautrius duomenis ar net sunaikinti visą sistemą.
Kaip veikia deserialization atakos praktikoje
Pažiūrėkime į konkretų pavyzdį su Java programavimo kalba. Java naudoja serializaciją objektams išsaugoti ir perduoti. Problema ta, kad deserializacijos metu automatiškai iškvieciami tam tikri objektų metodai, pavyzdžiui, readObject(). Jei užpuolikas gali kontroliuoti, kokie objektai deserializuojami, jis gali sukurti tokią objektų grandinę, kuri galiausiai įvykdys norimą kodą.
Klasikinis pavyzdys – Apache Commons Collections biblioteka turėjo klasių, kurios deserializacijos metu galėjo inicijuoti komandų vykdymą. Užpuolikas galėjo sukurti specialiai paruoštą serializuotą objektą, kuris deserializacijos metu paleistų bet kokią sisteminę komandą serveryje. Skamba baisu? Taip ir yra.
Python taip pat turi savo problemų su pickle moduliu. Nors dokumentacija aiškiai įspėja, kad niekada nereikėtų deserializuoti duomenų iš nepatikimų šaltinių, daugelis programuotojų šį įspėjimą ignoruoja. Štai paprastas pavyzdys, kaip galima išnaudoti pickle:
import pickle
import os
class Exploit:
def __reduce__(self):
return (os.system, ('rm -rf /',))
serialized = pickle.dumps(Exploit())
# Jei kažkas deserializuos šį objektą...
pickle.loads(serialized) # Katastrofa!
PHP taip pat nėra išimtis. unserialize() funkcija istoriškai buvo daugelio pažeidžiamumų šaltinis. Populiarios CMS sistemos kaip WordPress ar Drupal ne kartą turėjo kritinių spragų, susijusių su nesaugiu deserialization.
Kodėl šios spragos tokios pavojingos
Dauguma kitų pažeidžiamumų turi tam tikrus apribojimus. SQL injection leidžia manipuliuoti duomenų baze, XSS – vykdyti kodą naršyklėje. Bet insecure deserialization gali suteikti užpuolikui Remote Code Execution (RCE) galimybes – tai aukščiausias pavojaus lygis.
Kai užpuolikas gali vykdyti savavališką kodą serveryje, jis gali:
– Įdiegti backdoor’us ir išlaikyti prieigą ilgam laikui
– Pavogti visus duomenų bazės duomenis
– Modifikuoti aplikacijos logiką
– Naudoti serverį kaip atramtinį tašką kitiems išpuoliams
– Užšifruoti duomenis ir reikalauti išpirkos
Dar blogiau – tokias spragas dažnai sunku aptikti. Jos neatsiranda dėl akivaizdžių klaidų kode, o dėl sudėtingų sąveikų tarp skirtingų bibliotekų ir frameworkų. Automatizuoti saugumo skaneriai ne visada jas sugauna, nes reikia gilaus aplikacijos logikos supratimo.
Realūs atvejai ir jų pasekmės
2015 metais buvo atskleista masyvė Java deserialization spraga, paveikusi tūkstančius aplikacijų. WebLogic, WebSphere, JBoss – visi pagrindiniai Java aplikacijų serveriai buvo pažeidžiami. Užpuolikai aktyviai naudojo šią spragą įsilaužimams į įmones.
Equifax duomenų nutekėjimas 2017 metais, kuris paveikė 147 milijonus žmonių, taip pat turėjo sąsajų su deserialization problemomis Apache Struts frameworke. Tai kainavo įmonei šimtus milijonų dolerių ir nepagydomas reputacijos žaizdas.
Jenkins, populiari CI/CD platforma, 2017 metais turėjo kritinę deserialization spragą, kuri leido neautentifikuotiems užpuolikams vykdyti kodą serveryje. Kadangi Jenkins dažnai turi prieigą prie kodo repozitorijų ir deployment sistemų, tokia spraga galėjo būti katastrofiška.
Kaip atpažinti pažeidžiamą kodą
Pirmiausia reikia identifikuoti vietas, kur jūsų aplikacija deserializuoja duomenis. Ieškokite tokių funkcijų ir metodų:
Java: ObjectInputStream.readObject(), XMLDecoder.readObject(), XStream, JSON bibliotekos su polymorphic type handling
Python: pickle.loads(), yaml.load() (be safe_load), jsonpickle
PHP: unserialize(), __wakeup(), __destruct() magic metodai
.NET: BinaryFormatter, NetDataContractSerializer, JavaScriptSerializer
Jei bet kuri iš šių funkcijų naudojama su duomenimis, ateinančiais iš nepatikimų šaltinių (HTTP užklausos, cookies, URL parametrai, išorinės API), turite rimtą problemą.
Atkreipkite dėmesį į kodo fragmentus, kur deserializuojami duomenys iš:
– Vartotojo cookies
– HTTP headerių
– POST/GET parametrų
– Failų, kuriuos vartotojai gali įkelti
– Išorinių API atsakymų
– Message queue sistemų (RabbitMQ, Kafka)
Gynybos strategijos ir geriausios praktikos
Pats saugiausias būdas – visiškai išvengti objektų deserialization su nepatikimais duomenimis. Bet realybėje tai ne visada įmanoma. Štai ką galite daryti:
Naudokite saugius duomenų formatus
Vietoj objektų serializacijos naudokite paprastesnius formatus kaip JSON ar XML. Bet atsargiai – net JSON bibliotekos gali turėti deserialization problemų, jei jos palaiko type handling (pvz., Jackson su enableDefaultTyping).
Whitelist požiūris
Jei privalote naudoti deserialization, griežtai apribokite, kokie klasių tipai gali būti deserializuojami. Java galite naudoti ObjectInputFilter:
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.jusupakas.safe.*;!*"
);
objectInputStream.setObjectInputFilter(filter);
Kriptografiniai parašai
Pasirašykite serializuotus duomenis HMAC ar panašiu algoritmu. Prieš deserializuojant, patikrinkite parašą. Taip užpuolikas negalės modifikuoti duomenų:
// Serializacijos metu
byte[] serialized = serialize(object);
byte[] signature = hmac(serialized, secretKey);
// Saugokite abu kartu
// Deserializacijos metu
if (!verifyHmac(serialized, signature, secretKey)) {
throw new SecurityException("Invalid signature");
}
Izoliuokite deserialization procesą
Vykdykite deserialization atskirame procese su minimaliomis teisėmis. Naudokite konteinerizaciją (Docker) ar sandbox aplinkas. Jei kas nors nutiks, žala bus apribota.
Testavimas ir pažeidžiamumų paieška
Norint rasti deserialization spragas, reikia specifinių įrankių ir žinių. Štai keletas būdų:
Ysoserial – puikus įrankis Java deserialization spragoms testuoti. Jis turi įvairių payload’ų, skirtų skirtingoms bibliotekoms ir frameworkams:
java -jar ysoserial.jar CommonsCollections6 'calc.exe' | base64
Burp Suite su Java Deserialization Scanner plėtiniu gali automatiškai aptikti potencialias spragas. Jis bando įvairius payload’us ir stebi serverio atsakymus.
Rankinis testavimas taip pat svarbus. Pabandykite modifikuoti serializuotus objektus ir stebėkite, kaip aplikacija reaguoja. Ar ji meta klaidas? Ar galite įterpti netikėtus duomenis?
Kodo peržiūros metu ieškokite:
– Deserialization funkcijų be input validacijos
– Trečiųjų šalių bibliotekų su žinomomis spragomis
– Custom serialization logikos su magic metodais
– Type confusion galimybių
Ką daryti, jei radote spragą
Pirmas žingsnis – nedelsiant įvertinti riziką. Ar spraga yra viešai prieinama? Ar ji reikalauja autentifikacijos? Kokie duomenys gali būti paveikti?
Trumpalaikis sprendimas – išjunkite pažeidžiamą funkcionalumą, jei įmanoma. Jei tai kritinė sistemos dalis, įdiekite papildomus saugumo sluoksnius:
– WAF (Web Application Firewall) taisykles
– Rate limiting
– IP whitelist’us
– Papildomą autentifikaciją
Ilgalaikis sprendimas – refaktorinkite kodą. Pakeiskite nesaugią deserialization į saugesnę alternatyvą. Atnaujinkite bibliotekų versijas. Įdiekite automated security testing į CI/CD pipeline.
Nepamirškite dokumentuoti incidentą. Kas rado spragą? Kada? Kaip buvo išspręsta? Ši informacija pravers ateityje ir padės išvengti panašių problemų.
Saugumo kultūra ir nuolatinis budrumas
Deserialization spragos – tai ne vien technologinė problema, bet ir organizacinės kultūros klausimas. Komandos turi suprasti šių pažeidžiamumų rimtumą ir žinoti, kaip jų išvengti.
Reguliarūs security training’ai programuotojams yra būtini. Ne teoriniai seminarai, o praktiniai užsiėmimai, kur žmonės patys bando išnaudoti spragas kontroliuojamoje aplinkoje. Tai kur kas efektyviau nei PowerPoint prezentacijos.
Dependency management – kritiškai svarbu. Naudokite įrankius kaip OWASP Dependency-Check, Snyk ar GitHub Dependabot, kurie automatiškai praneša apie žinomas spragas naudojamose bibliotekose. Bet nepakanka tik gauti pranešimų – reikia ir greitai reaguoti.
Security code review turėtų būti standartinė praktika. Ne tik senior developeriai, bet visi komandos nariai turėtų mokėti atpažinti pavojingus kodo šablonus. Pull request’uose turėtų būti tikrinama ne tik funkcionalumas, bet ir saugumas.
Penetration testing bent kartą per metus – privaloma. Samdykite išorinius specialistus, kurie pažvelgs į jūsų sistemą šviežiu žvilgsniu. Jie dažnai randa tai, ko vidaus komanda nepastebėjo.
Bug bounty programos gali būti puikus papildomas saugumo sluoksnis. Tūkstančiai etinių hakerių visame pasaulyje ieško spragų, ir geriau, kad jie jas rastų už atlygį, nei kenkėjai – už jūsų klientų duomenis.
Insecure deserialization gali atrodyti kaip nišinė, techninė problema, bet realybėje tai viena iš pavojingiausių spragų šiuolaikinėse aplikacijose. Gera žinia ta, kad su tinkamomis žiniomis ir praktikomis ją galima efektyviai valdyti. Blogoji – kad daugelis organizacijų apie ją net negalvoja, kol neįvyksta incidentas. Būkite proaktyvūs, investuokite į saugumą dabar, ir išvengsite skausmingų pamokų ateityje. Jūsų klientai ir jūsų miego kokybė už tai padėkos.
