2011-08-20 16 views
6

सी में, POSIX कॉल का उपयोग करके, मैं यह निर्धारित कैसे कर सकता हूं कि पथ लक्ष्य निर्देशिका के अंदर है या नहीं?यह निर्धारित करने के लिए कि कोई पथ निर्देशिका के अंदर है या नहीं? (POSIX)

उदाहरण के लिए, वेब सर्वर की रूट निर्देशिका /srv में है, यह getcwd() डिमन के लिए है। /index.html के लिए अनुरोध को पार्स करते समय, यह /srv/index.html की सामग्री देता है।

मैं /srv के बाहर पथों के अनुरोधों को कैसे फ़िल्टर कर सकता हूं?

/../etc/passwd, /valid/../../etc/passwd, आदि

विभाजन / पर पथ और किसी भी .. युक्त वैध टूट जाएगा सरणी खारिज /srv/valid/../index.html तक पहुँचता है।

क्या सिस्टम कॉल के साथ ऐसा करने का एक कैननिक तरीका है? या क्या मुझे पथ को मैन्युअल रूप से चलने और निर्देशिका गहराई की गणना करने की आवश्यकता है?

+2

मुझे लगता है कि यही कारण है कि 'क्रोट (2)' का आविष्कार किया गया था! –

+0

@ करल नोरम: यदि आप किसी को सीमित खोल पहुंच देते हैं तो chroot बेहतर होता है। यदि आप अपने द्वारा बनाए गए किसी प्रोग्राम तक पहुंच सीमित नहीं करना चाहते हैं, तो chroot से बेहतर विकल्प हैं। – Dani

उत्तर

6

वहाँ हमेशा realpath है:

realpath() समारोह निकाले जाते हैं जाएगा, से पथ नाम से * file_name *, एक निरपेक्ष पथ नाम है कि एक ही निर्देशिका प्रवेश ले कर जाता है, जिसका संकल्प को शामिल नहीं करता की ओर इशारा किया '।' , '..', या प्रतीकात्मक लिंक।

तो तुलना करें कि realpath आपको अपनी वांछित रूट निर्देशिका देता है और देखें कि वे मेल खाते हैं या नहीं।

आप "/srv" को प्रीपेड करने से पहले डबल-डॉट्स का विस्तार करके फ़ाइल नाम को भी साफ कर सकते हैं। आने वाले पथ को स्लेश पर विभाजित करें और टुकड़े से टुकड़े टुकड़े करें। यदि आपको "." मिलता है तो उसे हटा दें और आगे बढ़ें; अगर आपको ".." मिलता है, तो इसे हटाएं और पिछले घटक (देखभाल को अपनी सूची में पहली प्रविष्टि से पहले नहीं लेना); अगर आपको कुछ और मिलता है, तो बस अगले घटक पर जाएं। फिर घटकों के बीच स्लेश के साथ एक साथ वापस क्या चिपकाएं और अपने "/srv/" को प्रीपेड करें। तो अगर कोई आपको "/valid/../../etc/passwd" देता है, तो आप "/srv/etc/passwd" और "/where/is/../pancakes/house" के साथ समाप्त हो जाएंगे "/srv/where/pancakes/house" के रूप में समाप्त हो जाएगा।

इस तरह आप "/srv" बाहर और एक इनकमिंग "/../.." (बेशक सांकेतिक लिंक के माध्यम से छोड़कर) नहीं मिल सकता है "/" (सिर्फ एक सामान्य फाइल सिस्टम में की तरह) के रूप में ही किया जाएगा। लेकिन अगर आप "/srv" के तहत प्रतीकात्मक के बारे में चिंतित हैं तो भी आप realpath का उपयोग करना चाहते हैं।

घटक द्वारा पथ नाम घटक के साथ काम करने से आप बाहरी दुनिया में मौजूद लेआउट और वास्तविक फ़ाइल सिस्टम लेआउट के बीच कनेक्शन तोड़ने की अनुमति भी देंगे; "/this/that/other/thing" को किसी भी वास्तविक "/srv/this/that/other/thing" फ़ाइल पर मानचित्र करने के लिए कोई आवश्यकता नहीं है, पथ किसी फ़ंक्शन कॉल के लिए किसी प्रकार के डेटाबेस या किसी प्रकार के नेमस्पेस पथ में केवल एक कुंजी हो सकता है।

0

आपको बस .. स्वयं को संसाधित करना चाहिए और जब यह पाया जाता है तो पिछले पथ घटक को हटा दें, ताकि फाइलों को खोलने के लिए उपयोग की जाने वाली अंतिम स्ट्रिंग में .. की कोई घटना न हो।

2

यह निर्धारित करने के लिए कि कोई फ़ाइल एफ निर्देशिका डी के भीतर है या नहीं, पहले स्टेट डी को अपना डिवाइस नंबर और इनोड नंबर निर्धारित करने के लिए (स्ट्रक्चर स्टेट के सदस्य st_dev और st_ino) निर्धारित करने के लिए।

फिर स्टेट एफ यह निर्धारित करने के लिए कि यह निर्देशिका है या नहीं। यदि नहीं, तो उसमें निर्देशिका का नाम निर्धारित करने के लिए बेसनाम नाम दें। इस निर्देशिका के नाम पर जी सेट करें। अगर एफ पहले से ही एक निर्देशिका थी, तो जी = एफ सेट करें।

अब, एफ डी के भीतर है और केवल अगर जी डी के भीतर है। अगला हमारे पास एक लूप है।

while (1) { 
    if (samefile(d_statinfo.d_dev, d_statinfo.d_ino, G)) { 
    return 1; // F was within D 
    } else if (0 == strcmp("/", G) { 
    return 0; // F was not within D. 
    } 
    G = dirname(G); 
} 

samefile समारोह सरल है:

int samefile(dev_t ddev, ino_t dino, const char *path) { 
    struct stat st; 
    if (0 == stat(path, &st)) { 
    return ddev == st.st_dev && dino == st.st_no; 
    } else { 
    throw ...; // or return error value (but also change the caller to detect it) 
    } 
} 

यह POSIX फ़ाइल सिस्टम पर काम करेंगे। लेकिन कई फाइल सिस्टम पॉज़िक्स नहीं हैं। देखने के लिए समस्याएं शामिल हैं:

  1. फाइल सिस्टम जहां डिवाइस/इनोड अद्वितीय नहीं है। कुछ FUSE फाइल सिस्टम इस के उदाहरण हैं; जब कभी अंतर्निहित फाइल सिस्टम में नहीं होता है तो वे कभी-कभी इनोड संख्या बनाते हैं। उन्हें इनोड संख्याओं का फिर से उपयोग नहीं करना चाहिए, लेकिन कुछ FUSE फाइल सिस्टम में बग हैं।
  2. टूटा एनएफएस कार्यान्वयन। कुछ प्रणालियों पर सभी एनएफएस फाइल सिस्टम के पास एक ही डिवाइस नंबर होता है। यदि वे सर्वर पर मौजूद होने पर इनोड नंबर से गुजरते हैं, तो इससे कोई समस्या हो सकती है (हालांकि मैंने इसे अभ्यास में कभी नहीं देखा है)।
  3. लिनक्स बाध्य माउंट पॉइंट। यदि /a/b का एक बाध्य माउंट है, तो /a/1/a के अंदर सही ढंग से प्रतीत होता है, लेकिन ऊपर दिए गए कार्यान्वयन के साथ /b/1/a के अंदर भी प्रतीत होता है। मुझे लगता है कि शायद यह सही जवाब है। हालांकि, यदि यह परिणाम आपको पसंद नहीं है, तो पथ नामों की तुलना करने के लिए strcmp() पर कॉल करने के लिए return 1 केस को बदलकर यह आसानी से तय किया जा सकता है। हालांकि, इसके लिए आपको काम करने के लिए realpath दोनों को एफ और डी पर कॉल करके शुरू करना होगा। realpath कॉल काफी महंगा हो सकता है (क्योंकि इसे डिस्क को कई बार हिट करने की आवश्यकता हो सकती है)।
  4. विशेष पथ //foo/bar। POSIX // से शुरू होने वाले पथ नामों को एक ऐसे तरीके से विशेष करने की अनुमति देता है जो कुछ हद तक अच्छी तरह परिभाषित नहीं है। वास्तव में मैं पॉज़िक्स प्रदान करता है कि अर्थशास्त्र के बारे में गारंटी के सटीक स्तर को भूल जाते हैं। मुझे लगता है कि उसी फ़ाइल को संदर्भित करने के लिए POSIX //foo/bar और //baz/ugh की अनुमति देता है। डिवाइस/इनोड जांच अभी भी आपके लिए सही चीज करनी चाहिए लेकिन आपको लगता है कि यह नहीं है (यानी आप पाते हैं कि //foo/bar और //baz/ugh एक ही फ़ाइल को संदर्भित कर सकते हैं लेकिन अलग-अलग डिवाइस/इनोड नंबर हैं)।

इस उत्तर मानता है कि हम दोनों एफ और डी यदि यह आप कुछ realpath() और getcwd() का उपयोग करके रूपांतरण करने के लिए आवश्यकता हो सकती है इसकी गारंटी नहीं है के लिए एक पूर्ण पथ के साथ शुरू करते हैं। यह एक समस्या होगी यदि वर्तमान निर्देशिका का नाम PATH_MAX से अधिक लंबा है (जो निश्चित रूप से हो सकता है)।

संबंधित मुद्दे

 संबंधित मुद्दे