Šablonų įterpimo pažeidžiamumas

Kas tai per žvėris ir kodėl turėtum juo rūpintis

Kai kurios saugumo spragos skamba egzotiškai ir atrodo, kad jos tikrai nepasieks tavo projekto. Template injection priklauso prie tų, kurios dažnai lieka radaro apačioje, kol staiga nepavirsta rimta problema. Esmė paprasta: jei leidžiame vartotojams įterpti duomenis, kurie vėliau apdorojami kaip šablono kodas, atsiveria durys į serverio valdymą.

Šablonų varikliai (template engines) yra visur – nuo Python Flask su Jinja2 iki Node.js su Handlebars ar Pug. Jie leidžia atskirti logiką nuo vizualinės dalies, todėl programuotojai juos myli. Problema kyla tada, kai į šabloną patenka nepatikrinti vartotojo duomenys, kurie vėliau interpretuojami kaip kodas, o ne kaip paprastas tekstas.

Pavyzdžiui, jei tavo aplikacija leidžia vartotojui sukurti personalizuotą sveikinimo žinutę ir naudoja šabloną tipo „Sveiki, {{name}}!”, viskas atrodo nekalčiai. Bet jei vietoj vardo kažkas įterps {{7*7}} ir sistema išves „49” – tai reiškia, kad turime template injection problemą. O jei sistema interpretuoja tokį kodą, galime eiti daug toliau nei paprasti matematiniai veiksmai.

Server-side vs Client-side: dvi skirtingos istorijos

Template injection turi dvi pagrindines atmaines, ir jas svarbu atskirti, nes poveikis ir eksploatavimo metodai skiriasi kardinaliai.

Server-Side Template Injection (SSTI) yra rimtesnis variantas. Čia kodas vykdomas serveryje, todėl sėkminga ataka gali suteikti pilną serverio kontrolę. Įsivaizduok situaciją: naudoji Jinja2 Python projekte ir kažkur kode tiesiog konkatuoji vartotojo įvestį su šablonu:

template = "Sveiki, " + user_input + "!"
render_template_string(template)

Jei user_input būtų kažkas panašaus į {{config.items()}}, užpuolikas galėtų pamatyti visą aplikacijos konfigūraciją, įskaitant slaptus raktus. O su tinkamu payload galima vykdyti sistemos komandas.

Client-Side Template Injection (CSTI) vyksta naršyklėje. Nors tai atrodo mažiau pavojinga, vis tiek gali sukelti XSS (Cross-Site Scripting) atakas ir duomenų vagystes. Angular, Vue ar kiti frontend frameworkai taip pat turi šablonų sistemą, ir netinkamas jų naudojimas gali tapti saugumo spraga.

Kaip užpuolikai išnaudoja šias spragas

Praktikoje template injection eksploatavimas vyksta keliais etapais. Pirmiausia reikia nustatyti, ar spraga egzistuoja. Užpuolikai dažnai pradeda nuo paprastų testų – įterpia {{7*7}}, ${7*7} ar <%= 7*7 %> priklausomai nuo to, kokį template engine spėja esant naudojamą.

Jei sistema išveda „49” vietoj pačios eilutės, tai patvirtinimas, kad kodas interpretuojamas. Toliau prasideda template engine identifikavimas. Skirtingi varikliai turi skirtingą sintaksę ir galimybes, todėl reikia suprasti, su kuo dirbi.

Pavyzdžiui, Jinja2 leidžia pasiekti Python objektus per __mro__ atributą ir __subclasses__() metodą. Tai reiškia, kad užpuolikas gali rasti klasę, kuri leidžia vykdyti sistemos komandas. Konkretus payload gali atrodyti baisiai sudėtingai:

{{''.__class__.__mro__[1].__subclasses__()[396]('cat /etc/passwd',shell=True,stdout=-1).communicate()}}

Twig (PHP template engine) turi savo kelius – galima pasiekti _self.env ir per jį manipuliuoti aplinka. Freemarker (Java) leidžia įkelti klases ir vykdyti kodą per freemarker.template.utility.Execute.

Realūs atvejai ir jų pamokos

Vienas garsesnių template injection atvejų buvo susijęs su Uber. 2016 metais saugumo tyrėjas rado SSTI spragą jų sistemoje, kuri leido pasiekti vidinę infrastruktūrą. Nors Uber greitai pašalino problemą, tai parodė, kaip net didelės kompanijos su rimtomis saugumo komandomis gali praleisti tokias spragas.

Kitas įdomus pavyzdys – įvairūs email marketing įrankiai, kurie leidžia vartotojams kurti personalizuotus laiškus su dinaminiu turiniu. Jei toks įrankis netinkamai apdoroja šablonus, užpuolikas gali ne tik skaityti kitų vartotojų duomenis, bet ir vykdyti komandas serveryje, kuriame veikia email sistema.

Mačiau atvejį, kai e-komercijos platforma leido pardavėjams kurti pasirinktines produktų aprašymo šablonus. Kūrėjai manė, kad apribodami kai kurias funkcijas užtikrina saugumą, bet nepastebėjo, kad per objektų hierarchiją vis tiek galima pasiekti pavojingas funkcijas. Rezultatas – vienas „kūrybingas” vartotojas galėjo matyti kitų pardavėjų užsakymus ir klientų duomenis.

Kaip apsisaugoti: praktiniai patarimai

Pirmasis ir svarbiausias principas – niekada nekonkatuok vartotojo įvesties tiesiogiai su šablonais. Jei naudoji Python Flask su Jinja2, visada naudok parametrus:

# BLOGAI
template = render_template_string("Sveiki, " + name)

# GERAI
template = render_template_string("Sveiki, {{name}}", name=name)

Antrasis dalykas – naudok sandboxing. Daugelis template engine turi apribotus režimus. Jinja2 turi SandboxedEnvironment, kuris riboja prieigas prie pavojingų funkcijų. Bet atsargiai – sandbox bypass technikos egzistuoja, todėl tai neturėtų būti vienintelė apsaugos linija.

Validuok ir filtruok įvestį agresyviai. Jei leidžiame vartotojams kurti šablonus, apribokime, ką jie gali daryti. Whitelist požiūris veikia geriau nei blacklist – apibrėžk, kas leidžiama, o ne ką draudžiama.

Jei projektas leidžia vartotojams kurti dinaminį turinį, apsvarstyk alternatyvas šablonams. Galbūt pakanka paprasto string formatting su aiškiai apibrėžtais kintamaisiais? Markdown su ribotomis galimybėmis? Ne visada reikia pilno template engine galios.

Testavimas ir aptikimas

Automatizuoti įrankiai gali padėti rasti template injection spragas, bet jie nėra tobuli. Burp Suite turi Template Injection extension, kuris gali identifikuoti akivaizdžius atvejus. Tplmap yra specializuotas įrankis SSTI testavimui – jis palaiko daugybę template engine ir turi įvairių payload.

Tačiau geriausias būdas – code review su saugumo perspektyva. Ieškokite vietų, kur vartotojo duomenys patenka į render_template_string, eval, exec ar panašias funkcijas. Atkreipkite dėmesį į dinamiškai generuojamus šablonus.

Praktinis patarimas: sukurkite testų rinkinį su įvairiais template injection payload ir reguliariai vykdykite juos prieš production. Įtraukite testus į CI/CD pipeline. Pavyzdinis test case galėtų atrodyti taip:

def test_template_injection():
payloads = ['{{7*7}}', '${7*7}', '<%= 7*7 %>']
for payload in payloads:
response = app.post('/endpoint', data={'input': payload})
assert '49' not in response.text

Specifiniai atvejai skirtingose technologijose

Python (Jinja2, Mako) – dažniausiai sutinkama problema yra render_template_string su vartotojo įvestimi. Jinja2 turi daug vidinių objektų, kuriuos galima pasiekti per __mro__ ir __subclasses__. Naudokite SandboxedEnvironment ir autoescape=True.

JavaScript (Handlebars, Pug, EJS) – client-side template injection dažnai virsta XSS. Server-side Node.js aplikacijose EJS su eval gali būti ypač pavojingas. Naudokite {{{ }}} vietoj {{ }} tik tada, kai tikrai reikia neescaped HTML, ir niekada su vartotojo duomenimis.

PHP (Twig, Smarty) – Twig yra gana saugus pagal nutylėjimą, bet netinkamas naudojimas vis tiek gali sukelti problemų. Smarty turi {php} tagus, kurie turėtų būti visiškai išjungti production aplinkoje.

Java (Freemarker, Velocity) – Freemarker leidžia įkelti klases per new() direktyvą. Tai turėtų būti apribota konfigūracijoje. Velocity taip pat turi pavojingų funkcijų, kurias galima pasiekti per ClassTool.

Kai spraga jau rasta: kas toliau

Jei aptikote template injection spragą savo sistemoje arba gavo pranešimą iš bug bounty programos, pirmiausia – nepanikuokite, bet ir nevilkinkite. Įvertinkite, kokio tipo spraga tai yra ir koks galimas poveikis.

SSTI su galimybe vykdyti komandas serveryje yra kritinė spraga – reikia taisyti nedelsiant. Patikrinkite logus, ar kas nors jau bandė eksploatuoti. Jei sistemoje yra jautrūs duomenys, apsvarstykite, ar nereikia pranešti paveiktiems vartotojams.

Taisymo procesas turėtų apimti ne tik konkrečios vietos pataisymą, bet ir visų panašių vietų kode paiešką. Jei viena vieta buvo pažeidžiama, tikėtina, kad yra daugiau panašių atvejų.

Po pataisymo atlikite penetration testing, kad įsitikintumėte, jog spraga tikrai pašalinta ir neatsivėrė naujos. Dokumentuokite, kas nutiko, kaip buvo išspręsta ir kokios pamokos išmoktos. Atnaujinkite savo secure coding guidelines, kad ateityje panašių klaidų būtų išvengta.

Ką daryti, kad rytoj būtų geriau

Template injection nėra nauja problema, bet ji vis dar aktualiai pasitaiko. Pagrindinė priežastis – nepakankamas supratimas, kaip veikia template engines ir kur slypi pavojai. Kūrėjai dažnai mato šablonus kaip paprastą string replacement mechanizmą, nors iš tikrųjų tai pilnavertė kodo vykdymo aplinka.

Investuokite į komandos mokymą. Saugumo mokymai neturėtų būti vienkartiniu renginiu – tai turėtų būti nuolatinis procesas. Įtraukite template injection pavyzdžius į jūsų onboarding procesą naujiems kūrėjams.

Naudokite static analysis įrankius, kurie gali aptikti pavojingus šabloninių funkcijų naudojimo atvejus. Semgrep, CodeQL ir panašūs įrankiai gali būti sukonfigūruoti ieškoti specifinių pattern’ų, kurie rodo galimas template injection spragas.

Ir svarbiausia – sukurkite kultūrą, kur saugumas yra kiekvieno atsakomybė, o ne tik security komandos. Kai kūrėjas rašo kodą, kuris dirba su vartotojo įvestimi ir šablonais, jis turėtų automatiškai galvoti apie saugumo implikacijas. Tai pasiekiama ne per vieną dieną, bet per nuoseklų darbą, gerų pavyzdžių demonstravimą ir atvirą komunikaciją apie saugumo incidentus ir pamokos iš jų.

Template injection gali atrodyti kaip egzotiška problema, bet realybėje tai viena iš tų spragų, kuri gali turėti katastrofiškų pasekmių. Geroji žinia – ją nesunku išvengti, jei žinai, ko saugotis ir laikaisi gerųjų praktikų. Investicija į prevenciją visada pigesnė nei incidento valdymas.

Daugiau

„ArgoCD GitOps“: „Kubernetes“ diegimas