OS command injection'i usaldusväärne lahendus on lõpetada kasutaja mõjutatavate sõnede andmine shellile. Parem escape'imine on nõrgem valik. Praktiline töö on leida kõik kohad, kus teie kood käivitab protsessi, ja otsustada iga koha puhul, kas see peab üldse olemas olema.
Command injection tekib siis, kui rakendus annab ebaturvalised kasutajaandmed süsteemi shellile ning süstitud käsud töötavad tavaliselt rakenduse õigustes. CWE-78 lisab ebameeldiva üksikasja: kuna käsud käivitab rakendus, võib tegevus paista pärinevat rakenduselt või selle omanikult.
Miks escape'imine on vale esimene vastus
PHP funktsioon escapeshellarg() ümbritseb väärtuse ülakomadega, nii et shell käsitleb seda ühe argumendina. See takistab & echo "hello" käivitumist eraldi käsuna. See ei takista ründajal valimast, millist ühte argumenti programm saab.
Iga OS command injection on ühtlasi ka argument injection. OWASP spikris prindib curlile antud escape("--help") ikkagi abiteksti. Sõltuvalt programmist ulatub mõju teabe avalikuks tulekust kuni kaugkoodi täitmiseni.
Lugejamärkus PHP käsiraamatu leheküljel escapeshellarg() näitab sama viga tari abil. Iga failinimi on õigesti escape'itud, kuid sidekriipsuga algav väärtus loetakse ikkagi valikuks ja taril on valik, mis käivitab mõne teise käsu. Kriipsude -- lisamine enne failinimesid parandab selle, kuid märkuse autor toob välja, et mitte iga programm seda eraldajat ei toeta.
Windowsis ütleb käsiraamat, et escapeshellarg() kasutab jutumärke ning asendab protsendimärgid, hüüumärgid ja jutumärgid tühikutega. Lugejamärkus lisab, et tulemus on turvaline, kuid kadudega. Mõlemal juhul on see põhjus shelli vältida, mitte lahendus.
Leidke kutsed üles
Käivitage need otsingud kõigis hoidlates. Kohandage failitüübid oma struktuurile vastavaks:
rg -n 'shell=True|os\.system|os\.popen' --type py
rg -n "subprocess\.(run|Popen|call|check_output)\(\s*[f\"']" --type py
rg -n '\b(system|exec|shell_exec|passthru|popen|proc_open)\s*\(' --type php
rg -n '`[^`]*\$' --type php
rg -n 'Runtime\.getRuntime\(\)\.exec|ProcessBuilder' --type java
rg -n 'ProcessStartInfo|Process\.Start|UseShellExecute' --type cs
rg -n '\bsystem\s*\(|popen\s*\(|exec[lv]p?e?\s*\(' --type c
Teine Pythoni muster tabab juhud, kus käsuna antakse üks sõne. Ilma shell=True-ta töötab see ainult siis, kui sõne nimetab programmi ega midagi muud, seega tuleb iga tabamust käsitsi vaadata. PHP tagurpidi ülakomade muster on mürarikas, sest see sobib ka kommentaaride ja SQL-i tagurpidi ülakomadega. Otsige ka shelliskriptidest, juurutusskriptidest ja cron-wrapperitest, mis koostavad käske muutujatest sama sageli kui rakenduse kood.
| Keel | Otsige | Asendage |
|---|---|---|
| PHP | system(), exec(), shell_exec(), passthru(), popen(), proc_open(), tagurpidi ülakomade operaator | Sisseehitatud funktsioonidega, näiteks mkdir() |
| Python | shell=True; os.system; os.popen; üks käsusõne | subprocess.run koos loendiga |
| Java | Runtime.exec(String) | ProcessBuilder, kus programm ja iga argument on eraldi elemendid |
| .NET | cmd.exe, powershell.exe, sh -c, kokku ehitatud Arguments sõne | ProcessStartInfo koos ArgumentListiga |
| C | system() | Teegi kutse |
Kaasake ka kõvakodeeritud käsud. OWASP kogukonna ülevaade kirjeldab privilegeeritud CGI programmi, mis käivitab cd /var/yp && make ilma et sõnes oleks kasutaja sisendit. make jaoks polnud absoluutset teed kasutatud, nii et ründaja sai muuta $PATHi ja panna oma binaarfaili töötama programmi õigustes.
Sorteerige tabamused
Tabamuste toorloend ei ole leidude loend. Jälgige iga väärtust tagasi ja küsige, kas päring võib selleni jõuda. Ilmsed allikad on marsruudi- ja päringuparameetrid, vormiväljad, küpsised ja HTTP päised. Need, mida unustatakse, on üleslaaditud failinimed, andmebaasiväärtused, millesse kasutajad saavad kirjutada, järjekorrasõnumid ja keskkonnamuutujad.
Jagage tabamused kolme hunnikusse: kättesaadavad autentimata päringuga, kättesaadavad alles pärast sisselogimist ja üldse kättesaamatud. Parandage esimene hunnik esimesena. Hoidke kolmas loendis, sest hilisem refaktoreerimine võib selle kellegi märkamata päringuga ühendada.
Asendage kutse API-ga
Teegi sisseehitatud funktsioone ei saa panna tegema midagi muud peale nende ettenähtud ülesande. Kasutage system("mkdir /dir_name") asemel mkdir(). Kasutage Java meili API-t javax.mail.* selle asemel, et kutsuda shellist mail. CWE-78 annab sama nõu disainiks: kasutage teegi kutseid, mitte väliseid protsesse.
Kui peate protsessi käivitama
Mõnel tööriistal puudub teegi vaste. Sel juhul kasutage koos kahte kihti: argumendiloend ilma shellita ning valideerimine.
Pythoni subprocess'i dokumentatsioon ütleb, et argumentide jada on üldiselt eelistatum, sest moodul hoolitseb escape'imise ja jutumärkide eest. Selles on märge lugeda enne shell=True kasutamist läbi jaotis Security Considerations. Samuti soovitatakse käivitatava faili jaoks täielikku teed, mis kaotab PATHi otsingu, mida ülaltoodud make näide ära kasutab.
import re, subprocess
if not re.fullmatch(r"[a-z0-9]{3,10}", name):
raise ValueError("rejected")
subprocess.run(["/usr/bin/tar", "cf", "out.tar", "--", name], check=True)
Muster lükkab sidekriipsuga algava väärtuse juba tagasi, seega paistab -- üleliigne. See on kaitse sügavuti (defence in depth): kui keegi hiljem regulaaravaldist leevendab, takistab eraldaja ikkagi namei lugemist valikuna.
.NET-is kasutage FileName jaoks fikseeritud täielikku teed, määrake UseShellExecute = false ja lisage iga argument ArgumentList kaudu. Ärge kunagi andke ebausaldusväärset sisendit cmd.exe, powershell.exe ega sh -c käsule. Argumentide eraldamine ei ole valideerimine, ei Javas ega .NET-is.
Seejärel valideerige väärtused. Kontrollige käsku lubatud käskude loendi vastu ning iga argumenti lubatud väärtuste või mustri vastu, millel on maksimaalne pikkus ning kust on metamärgid ja tühikud välja jäetud.
Curli puhul ei piisa ---st, sest hilisemad argumendid on endiselt URL-i operandid ja täiendavad edastused on võimalikud. Andke täpselt üks valideeritud URL, kasutage --globoff ja rakendage SSRF-kaitset.
Käivitage rakendus väikseimate vajalike õigustega ja andke ühe ülesande töödele oma isoleeritud konto. Sandbox, näiteks chroot, AppArmor või SELinux, aitab, kuid CWE-78 hindab selle tõhusust piiratuks. See kitsendab, millele ründaja ligi pääseb, kuid ei kõrvalda viga.
Kust alustada
- Käivitage ülaltoodud otsingud kõigis hoidlates, kaasa arvatud juurutusskriptid, CI tööd ja cron-wrapperid. Loetlege iga tabamus koos sisendiga, mis selleni jõuab, ja sorteerige loend selle järgi, kas päring võib sinna jõuda.
- Küsige iga tabamuse kohta, kas sama töö teeb ära teegi või sisseehitatud funktsioon. Kui teeb, asendage kutse ja kustutage protsessi käivitamine.
- Kirjutage ülejäänud kutsed ümber nii, et need kasutaksid argumendiloendit, ei kasutaks shelli ja käivitatava faili absoluutset teed. Lisage lubatud väärtuste loendil põhinev valideerimine ja
--eraldaja, kus programm seda toetab. - Käivitage protsessid pühendatud vähese õigusega konto all, et märkamata jäänud juhul oleks ründajal vähem võimalusi.
- Testige iga allesjäänud kutset väärtusega nagu
bob.txt;sleep 5ja väärtusega, mis algab sidekriipsuga. Mõõtke vastuse aega, selle asemel et väljundit oodata. Sidekriipsuga algav väärtus peab olema tagasi lükatud või käsitletud failinimena. Välise kontrolli jaoks proovib penetratsioonitest mõlemat või meie turvalise tarkvaraarenduse meeskond saab koodi teiega koos üle vaadata.
