Feltöltött fájltípus: kliens vagy szerver oldalon dől el?

Most botlottam egy érdekes problémába.
Csináltam egy php alapú webalkalmazást, ahová fel kell tölteni egy .csv fájlt.
A biztonság kedvéért ráellenőrzök a fájl típusára is:

Ha ez a ($_FILES["file"]["type"] == "text/csv"), akkor továbbengedem a futást.
A linuxos böngészőmből simán lefutott egy adott inputra a program. Egy ismerősöm azonban Microsoft Windows operációs rendszer alól futtatta ugyanezzel a fájllal a webalkalmazást, és neki hibát adott. Az ő kedvéért be kellett tennem:
|| ($_FILES["file"]["type"] == "application/vnd.ms-excel")

Azt hittem korábban, hogy ezt a szerver dönti el, hogy az adott fájl milyen típusú. Ezek szerint ez nem így van.

Egyébként az utóbbi időben törekszem arra, hogy a parancssori szkriptjeimet átültetem webes alapúvá, hogy szakavatatlanok is tudják használni az intraneten. (Nagy segítség ehhez az exec() parancs, aminek visszatérési értéke is van.)

Ez persze sok biztonsági kérdést felvet. Ha vannak meglátásaitok (vagy hasznos doksijaitok) e téren, hogy mire érdemes vigyázni, akkor írjatok.

Hozzászólások

HTTP Content-Type fejléc.

Szerk: mármint ennek alapján sejthető lett volna, hogy kliens oldalon dől el.

Pl ha lehet, vagy kompletten ignorald az exec()-et es oldd meg mashogy, vagy legyel extrem paranoias a hivasanal. Konnyu sebezhetove tenni vele a gepet.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

A $_FILES tömbben levő mime type mindig a klienstől jön, ezért érdemes inkább a http://hu2.php.net/manual/en/function.mime-content-type.php vagy még jobb, az azon oldalon linkelt FileInfo függvényekkel megnézni (finfo_file-nál példakód is van hozzá).

Persze jó esetben php-ből nem tudsz a tmp-dir-ben mászkálni, úgyhogy move_uploaded_file()-al először át kell tenned egy saját ideiglenes helyre, aztán belenézni a fájl típusba, és ez alapján törölni az új ideiglenes helyről, vagy átrakni a véglegesre.

Az exec/shell_exec stb. ha tényleg jól használod (escapeshellarg, kőkeményen ellenőrizve a usertől érkező adatokat, változókat csak paraméterként használva etc., nem `scriptem.sh $_GET['foo']` formában) belefér, különösen, ha csak "saját" felhasználásra van (net felől nem érhető el). Azért amit lehet érdemesebb "natívra" átírni.

BlackY

Köszi az ötleteket!

Amúgy az exec-et mindig előre meghatározott paraméterrel hívom meg, soha nem felhasználói inputtal.
Azaz pl. "exec('./step1')".

A felhasználó csak egy-egy fájlt tud feltölteni, és azzal folyik a további manőverezés (legalábbis az eddigiekben így volt).

Megrágja a beadott fájlt és (letölthető) kimenetet gyárt. Esetenként ez több percig is eltart.

Amúgy (bármennyire is "rossz jel" és webfejlesztői szemmel veszélyeket rejtő megoldás ez) pont azt szeretem ebben a témában, hogy a parancssorban kikristályosított "step1, step2, ... stepn" lépéssort könnyen-gyorsan-fájdalommentesen át lehet ültetni webes "Tovább" gombokra, s így -- mint írtam -- szakavatatlan is végig tudja járni. Arra nincs idő/pénz, hogy minden egyes ilyen stepX-nek meggyártsam a natív php-s változatát, és intranetes környezetben nem is szükséges. Meg felesleges is lenne két karbantartandó ágat (parancssori/webes) csinálni.

az, hogy állítólag fix paramétereket ad át a programjának. (vagyis a php kimenete egy file, amit a step1 feldolgoz, fix paraméter esetén értelemszerűen ez a file név fix). Ha mocsok akarsz lenni, akkor szimplán párhuzamosan (akár két tabon) két file-t töltesz fel. Ilyenkor mivel azonos a file, a step1 meg minden esetben ugyanabból dolgozik, a második feltöltésnél step1 még dolgozhat, ami nem túl sok jót eredményez;)

Miert kene 2 branch (parancssori/webes)? AFAIK PHP megy parancssorban is, egyszer megirod az egy eszkozt, ami mindket modon megjeleniti a kimenetet, es hasznalod mind2 helyzetben. (Jomagam Perl/CGI-ben jopar ilyen toolt ganyoltam ossze.)

--
"It all keeps adding up / I think I'm cracking up / Am I just paranoid? / I'm just stoned"
/Green Day - Basket Case/

És a legfontosabb: ellenőrzöd, hogy valóban az jött-e meg, amit a kliens állít.

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Nem azt kéne ellenőrizni, hogy az jött-e, amit elvársz a klienstől?

A fenti példánál maradva (ahol a klienstől kapott mime típus alapján lehet, hogy xls fájlról van szó), ha te azt ellenörzöd, hogy a szerver és a kliensek mime fájlja (vagy registry-je!) ugyanazt tartalmazza-e, akkor amint jön egy IE user, azonnal nem használhatja. Vagy a használható mime típusok listája mellett mindegyikhez egy whitelist-et is karban kell tartani, hogy milyen "aliasokat" írhatnak rá a böngészők.

Akkor meg már egyszerűbb csak megnézni szerver oldalon, hogy ténylegesen mi jött a klienstől, a nem megfelelőket visszadobni, hogy hibás fájl, naplózni a hibás fájl típust (és hogy mit hazudott rá a browser), és ha blacklistelt fájltípus jön (pl. akármilyen text #! kezdettel, futtatható bináris stb.), akkor riasztást is küldeni (a visszadobás, és a bizonyíthatósághoz a biztonságos helyre back-upolás után).

BlackY

Bocs, elírtam. Azt ellenőrizzük, hogy azt kaptuk-e, amit vártunk.

(Jelen esetben CSV-t, olyan formátumban, ahogy mi várjuk és nem valami totál mást. Az, hogy a MIME type-ben mi szerepel.... nos, az jelen esetben felesleges adat.)

Soha nem jutott még eszembe a MIME type-al foglalkozni feltöltés esetén, mindig az adatból indultam ki.

----------------
Lvl86 Troll, "hobbifejlesztő" - Think Wishfully™

Szerintem a szerveren a megérkezett fájlt érdemes alávetni egy csv függvényes eljárásnak (fgetcsv pl), ha elhasal, át akarják verni a rendszert.

--
Coding for fun. ;)