यह कोड बफर अतिप्रवाह हमलों के लिए असुरक्षित क्यों है?


148
int func(char* str)
{
   char buffer[100];
   unsigned short len = strlen(str);

   if(len >= 100)
   {
        return (-1);
   }

   strncpy(buffer,str,strlen(str));
   return 0;
}

यह कोड एक बफर अतिप्रवाह हमले के लिए असुरक्षित है, और मैं यह पता लगाने की कोशिश कर रहा हूं कि क्यों। मैं सोच रहा हूँ कि यह lenएक के shortबजाय घोषित होने के साथ करना है int, लेकिन मैं वास्तव में निश्चित नहीं हूं।

कोई विचार?


3
इस कोड के साथ कई समस्याएँ हैं। याद रखें कि C स्ट्रिंग्स अशक्त हैं।
दिमित्री चुबरोव

4
@DmitriChubarov, स्ट्रिंग को समाप्त नहीं करने पर अशक्त होना केवल एक समस्या होगी यदि कॉल के बाद स्ट्रिंग का उपयोग किया जाता है strncpy। इस मामले में, यह नहीं है।
आर साहू

43
इस कोड में समस्याएं सीधे उस तथ्य से प्रवाहित होती हैं, जिसकी strlenगणना की जाती है, जिसका उपयोग वैधता जांच के लिए किया जाता है, और फिर इसे बेतुके तरीके से फिर से परिकलित किया जाता है - यह एक DRY विफलता है। यदि दूसरे strlen(str)को बदल दिया जाता है len, तो बफर के अतिप्रवाह की कोई संभावना नहीं होगी, भले ही किस प्रकार का हो len। जवाब इस बिंदु को संबोधित नहीं करते हैं, वे इसे से बचने के लिए प्रबंधित करते हैं।
जिम बेल्टर

3
@CiaPan: वेन नॉट-टर्मिनेटेड स्ट्रिंग को पास करने के लिए, स्ट्रलेन अपरिभाषित व्यवहार दिखाएगा।
केसरलुडी

3
@JimBalter नाह, मुझे लगता है कि मैं उन्हें वहाँ छोड़ दूँगा। हो सकता है कि किसी और को एक ही मूर्खतापूर्ण गलतफहमी होगी और इससे सीख लें। बेझिझक उन्हें ध्वजांकित करें यदि वे आपको परेशान करते हैं, तो कोई व्यक्ति उनके आसपास आ सकता है और उन्हें हटा सकता है।
असद सईदुद्दीन

जवाबों:


192

अधिकांश कंपाइलरों पर a का अधिकतम मान unsigned short65535 है।

ऊपर का कोई भी मान जिसके चारों ओर लिपटा होता है, इसलिए 65536 0 हो जाता है, और 65600 65 हो जाता है।

इसका मतलब यह है कि सही लंबाई के लंबे तार (जैसे 65600) चेक पास करेंगे, और बफर को ओवरफ्लो करेंगे।


size_tके परिणाम को संग्रहीत करने के लिए उपयोग करें strlen(), नहीं unsigned short, और lenएक अभिव्यक्ति की तुलना करें जो सीधे आकार को एन्कोड करता है buffer। उदाहरण के लिए:

char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0]))  return -1;
memcpy(buffer, str, len + 1);

2
@PatrickRoberts सैद्धांतिक रूप से, हाँ। लेकिन आपको यह ध्यान रखना होगा कि 90% रनटाइम के लिए 10% कोड जिम्मेदार है, इसलिए आपको सुरक्षा से पहले प्रदर्शन नहीं करने देना चाहिए। और ध्यान रखें कि समय के साथ कोड में परिवर्तन होता है, जिसका अचानक मतलब हो सकता है कि पिछला चेक चला गया है।
orlp

3
बफर अतिप्रवाह को रोकने के लिए, बस lenstrncpy के तीसरे तर्क के रूप में उपयोग करें । किसी भी मामले में फिर से स्ट्रलेन का उपयोग करना गूंगा है।
जिम बाल्टर

15
/ sizeof(buffer[0])- ध्यान दें कि sizeof(char)C हमेशा 1 होता है (भले ही एक चार में गजिलीन बिट्स हों) जब कि एक अलग डेटा प्रकार का उपयोग करने की कोई संभावना नहीं है, तो यह बहुत ही अच्छा है। फिर भी ... एक पूर्ण उत्तर के लिए यश (और टिप्पणियों के लिए उत्तरदायी होने के लिए धन्यवाद)।
जिम बाल्टर

3
@ rr-: char[]और char*एक ही चीज नहीं हैं। ऐसी कई स्थितियाँ हैं जिनमें एक char[]को संक्षेप में रूपांतरित कर दिया जाएगा char*। उदाहरण के लिए, फ़ंक्शन तर्कों के प्रकार के रूप में उपयोग किए जाने पर char[]बिल्कुल वैसा ही char*है। हालांकि, रूपांतरण के लिए नहीं होता है sizeof()
डिट्रीच एप्प

4
@Controll क्योंकि यदि आप bufferकिसी बिंदु पर आकार बदलते हैं तो अभिव्यक्ति स्वचालित रूप से अपडेट हो जाती है। यह सुरक्षा के लिए महत्वपूर्ण है, क्योंकि bufferहो सकता है कि घोषणा वास्तविक कोड में चेक से काफी कुछ दूर हो। इसलिए बफर के आकार को बदलना आसान है, लेकिन हर उस स्थान पर अपडेट करना भूल जाते हैं जहां आकार का उपयोग किया जाता है।
orlp

28

समस्या यहाँ है:

strncpy(buffer,str,strlen(str));
                   ^^^^^^^^^^^

यदि स्ट्रिंग लक्ष्य बफर की लंबाई से अधिक है, तो strncpy अभी भी इसे कॉपी करेगा। आप बफर के आकार के बजाय कॉपी करने के लिए स्ट्रिंग के पात्रों की संख्या को आधार बना रहे हैं। ऐसा करने का सही तरीका इस प्रकार है:

strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = '\0';

यह क्या करता है शून्य समाप्ति चरित्र के लिए बफर माइनस एक के वास्तविक आकार में कॉपी किए गए डेटा की मात्रा को सीमित करता है। फिर हम बफर में अंतिम बाइट को एक अतिरिक्त सुरक्षा के रूप में शून्य वर्ण में सेट करते हैं। इसका कारण यह है क्योंकि strncpy n बाइट्स तक की प्रतिलिपि बना देगा, जिसमें समाप्ति नल भी शामिल है, यदि strlen (str) <len - 1. यदि नहीं, तो null की प्रतिलिपि नहीं बनाई गई है और आपके पास एक क्रैश परिदृश्य है क्योंकि अब आपके बफ़र में एक अव्यवस्थित है स्ट्रिंग।

उम्मीद है की यह मदद करेगा।

संपादित करें: दूसरों से आगे की परीक्षा और इनपुट पर, फ़ंक्शन के लिए एक संभावित कोडिंग निम्नानुसार है:

int func (char *str)
  {
    char buffer[100];
    unsigned short size = sizeof(buffer);
    unsigned short len = strlen(str);

    if (len > size - 1) return(-1);
    memcpy(buffer, str, len + 1);
    buffer[size - 1] = '\0';
    return(0);
  }

चूँकि हम पहले से ही स्ट्रिंग की लंबाई जानते हैं, इसलिए हम स्ट्रिंग को उस स्थान से कॉपी करने के लिए मेम्पी का उपयोग कर सकते हैं, जिसे बफर में स्ट्रिंग द्वारा संदर्भित किया गया है। ध्यान दें कि strlen (3) (FreeBSD 9.3 सिस्टम पर) के लिए मैनुअल पेज के अनुसार, निम्नलिखित कहा गया है:

 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.

जो मैं व्याख्या करता हूं कि स्ट्रिंग की लंबाई में शून्य शामिल नहीं है। यही कारण है कि मैं अशक्त शामिल करने के लिए लेन + 1 बाइट्स की प्रतिलिपि बनाता हूं, और यह सुनिश्चित करने के लिए परीक्षण की जांच करता है कि लंबाई <बफर का आकार - 2. शून्य से एक क्योंकि बफर स्थिति 0 पर शुरू होता है, और शून्य से एक और सुनिश्चित करने के लिए एक कमरा है। अशक्त के लिए।

संपादित करें: बाहर निकलता है, कुछ का आकार 1 से शुरू होता है जबकि पहुंच 0 से शुरू होती है, इसलिए -2 पहले गलत था क्योंकि यह किसी भी चीज के लिए त्रुटि लौटाएगा> 98 बाइट लेकिन यह> 99 बाइट होना चाहिए।

EDIT: यद्यपि एक अहस्ताक्षरित शॉर्ट के बारे में उत्तर आम तौर पर सही होता है क्योंकि अधिकतम लंबाई जिसे 65,535 वर्णों का प्रतिनिधित्व किया जा सकता है, यह वास्तव में मायने नहीं रखता है क्योंकि यदि स्ट्रिंग उससे अधिक लंबी है, तो मान चारों ओर लपेट जाएगा। यह 75,231 (जो कि 0x000125DF है) लेने और शीर्ष 16 बिट्स को आप 9695 (0x000025DF) देने की तरह है। एकमात्र समस्या जो मुझे इसके साथ दिखाई देती है, वह 65,535 के पिछले 100 वर्णों की है, क्योंकि लंबाई की जाँच प्रतिलिपि की अनुमति देगी, लेकिन यह केवल सभी मामलों में स्ट्रिंग के पहले 100 वर्णों की नकल करेगा और स्ट्रिंग को समाप्त कर देगा । तो रैपराउंड मुद्दे के साथ, बफर अभी भी बह नहीं जाएगा।

यह स्ट्रिंग की सामग्री और आप इसके लिए क्या उपयोग कर रहे हैं, इसके आधार पर सुरक्षा जोखिम हो सकता है या नहीं। यदि यह केवल सीधे पाठ है जो मानव पठनीय है, तो आमतौर पर कोई समस्या नहीं है। आपको बस एक काट दिया गया स्ट्रिंग मिलता है। हालाँकि, अगर यह URL या SQL कमांड अनुक्रम जैसा कुछ है, तो आपको समस्या हो सकती है।


2
सच है, लेकिन यह सवाल के दायरे से परे है। कोड स्पष्ट रूप से फ़ंक्शन को एक चर सूचक पास करने के लिए दिखाता है। फ़ंक्शन के दायरे के बाहर, हमें परवाह नहीं है।
डेनियल रूडी

"बफर जिसमें स्ट्रिंग संग्रहीत है" - यह बफर अतिप्रवाह नहीं है , जो यहां मुद्दा है। और हर उत्तर में "समस्या" है, जो कि अपरिहार्य है, जिस पर हस्ताक्षर किए गए हैं func... और हर दूसरे सी फ़ंक्शन ने कभी लिखा है कि एनयूएल-टर्मिनेटेड स्ट्रिंग्स को तर्क के रूप में लेता है। इनपुट को NUL-समाप्त नहीं होने की संभावना को लाना पूरी तरह से क्लूलेस है।
जिम बाल्टर

"यह सवाल के दायरे से परे है" - जो दुख की बात है कि कुछ लोगों की समझ की क्षमता से परे है।
जिम बेल्टर

"समस्या यहाँ है" - आप सही कह रहे हैं, लेकिन आप अभी भी महत्वपूर्ण मुद्दे को याद कर रहे हैं, जो यह है कि परीक्षण ( len >= 100) एक मूल्य के खिलाफ किया गया था, लेकिन कॉपी की लंबाई को एक अलग मूल्य दिया गया था ... यह DRY सिद्धांत का उल्लंघन है। बस कॉलिंग strncpy(buffer, str, len)बफर ओवरफ्लो की संभावना से बचा जाता है, और इससे कम काम करता है strncpy(buffer,str,sizeof(buffer) - 1)... हालांकि यहां यह सिर्फ एक धीमी गति के बराबर है memcpy(buffer, str, len)
जिम बाल्टर

@JimBalter यह सवाल के दायरे से परे है, लेकिन मैं पछताता हूं। मैं समझता हूं कि परीक्षण द्वारा उपयोग किए गए मान और strncpy में उपयोग किए जाने वाले मूल्य दो अलग-अलग हैं। हालाँकि, सामान्य कोडिंग अभ्यास कहता है कि कॉपी की सीमा साइज़ोफ़ (बफर) होनी चाहिए - 1 इसलिए इससे कोई फर्क नहीं पड़ता कि कॉपी पर स्ट्रैस की लंबाई क्या है। strncpy बाइट्स की नकल करना बंद कर देगा जब यह या तो एक नल को हिट करेगा या n बाइट्स को कॉपी करेगा। अगली पंक्ति यह गारंटी देती है कि बफर में अंतिम बाइट एक शून्य चार है। कोड सुरक्षित है, मैं अपने पिछले बयान से खड़ा हूं।
डैनियल रूडी

11

भले ही आप उपयोग कर रहे हों strncpy, कटऑफ की लंबाई अभी भी पास किए गए स्ट्रिंग पॉइंटर पर निर्भर है। आपको पता नहीं है कि स्ट्रिंग कितनी लंबी है (पॉइंटर के सापेक्ष नल टर्मिनेटर का स्थान, वह है)। इसलिए strlenअकेले फोन करना आपको भेद्यता के लिए खोल देता है। यदि आप अधिक सुरक्षित होना चाहते हैं, तो उपयोग करें strnlen(str, 100)

पूर्ण कोड सही होगा:

int func(char *str) {
   char buffer[100];
   unsigned short len = strnlen(str, 100); // sizeof buffer

   if (len >= 100) {
     return -1;
   }

   strcpy(buffer, str); // this is safe since null terminator is less than 100th index
   return 0;
}

@ user3386109 strlenभी बफर के अंत तक पहुँच नहीं होगा ?
पैट्रिक रॉबर्ट्स

2
@ user3386109 जो आप इंगित कर रहे हैं, वह ऑरलपी के उत्तर को मेरे जैसे ही अमान्य बना देता है। मैं यह देखने में विफल रहता हूं कि strnlenअगर orlp सुझाव दे रहा है तो समस्या का समाधान क्यों नहीं होता है, वैसे भी यह सही है।
पैट्रिक रॉबर्ट्स

1
"मुझे नहीं लगता कि स्ट्रेंलेन यहाँ कुछ भी हल करता है" - बेशक यह करता है; यह अतिप्रवाह को रोकता है buffer। "चूंकि स्ट्रिंग 2 बाइट्स के बफर को इंगित कर सकती है, जिनमें से कोई भी NUL नहीं है।" - यह अप्रासंगिक है, क्योंकि यह किसी भी कार्यान्वयन का सच है func। यहां सवाल बफर ओवरफ्लो के बारे में है, यूबी के लिए नहीं क्योंकि इनपुट एनयूएल-टर्मिनेटेड नहीं है।
जिम बाल्टर

1
"Strnlen के लिए दिया गया दूसरा पैरामीटर उस ऑब्जेक्ट का आकार होना चाहिए जो पहला पैरामीटर इंगित करता है, या strnlen बेकार है" - यह पूर्ण और पूरी तरह से बकवास है। यदि strnlen का दूसरा तर्क इनपुट स्ट्रिंग की लंबाई है, तो strnlen, strlen के बराबर है। आप उस नंबर को कैसे प्राप्त करेंगे, और यदि आपके पास यह था, तो आपको str [n] लेन की आवश्यकता क्यों होगी? यही कारण है कि strnlen बिल्कुल नहीं है।
जिम बाल्टर

1
+1 हालांकि यह उत्तर अपूर्ण है क्योंकि यह ओपी कोड के समतुल्य नहीं है - strncpy NUL-pads और NUL समाप्त नहीं करता है, जबकि strcpy NUL-termates और NUL-pad नहीं है, यह समस्या को हल करता है, इसके विपरीत ऊपर से हास्यास्पद, अनभिज्ञ टिप्पणी।
जिम बाल्टर

4

लपेटने के साथ उत्तर सही है। लेकिन एक समस्या है जो मुझे लगता है कि उल्लेख नहीं किया गया था (len> = 100)

ठीक है अगर लेन 100 होगा तो हम 100 तत्वों की नकल करेंगे जो हमारे पास \ 0 नहीं होगा। इसका मतलब साफ है कि उचित समाप्त स्ट्रिंग के आधार पर किसी अन्य फ़ंक्शन का मूल सरणी से परे चलना होगा।

C से स्ट्रिंग प्रॉब्लम IMHO है जो न के बराबर है। आप कॉल करने से पहले कुछ हद तक बेहतर कर सकते हैं, लेकिन यह भी मदद नहीं करेगा। कोई सीमा जाँच नहीं है और इसलिए बफर ओवरफ्लो हमेशा हो सकता है और दुर्भाग्य से होगा ...।


स्ट्रिंग समस्याग्रस्त है व्याख्या करने योग्य: बस उपयुक्त कार्यों का उपयोग। अर्थात। नहीं strncpy() और दोस्तों, लेकिन स्मृति जैसे कार्यों strdup()और दोस्तों को आवंटित करती है । वे POSIX-2008 मानक में हैं, इसलिए वे काफी पोर्टेबल हैं, हालांकि कुछ मालिकाना प्रणालियों पर उपलब्ध नहीं हैं।
सेमीस्टर - मोनिका

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

आपका अवलोकन मुझे पथभ्रष्ट लगता है। if (len >= 100)जब जाँच विफल हो जाती है , तब नहीं जब यह पास हो जाता है, जिसका अर्थ है कि ऐसा कोई मामला नहीं है जहाँ कोई NUL टर्मिनेटर के साथ 100 बाइट्स को कॉपी नहीं किया जाता है, क्योंकि वह लंबाई विफल स्थिति में शामिल है।
पैट्रिक रॉबर्ट्स

@ सेमीस्टर। इस मामले में आप गलत हैं। यह हल करने योग्य नहीं है, क्योंकि एक हमेशा सीमाबद्ध तरीके से लिख सकता है। हां यह अपरिभाषित व्यवहार है लेकिन इसे पूरी तरह से रोकने का कोई तरीका नहीं है।
फ्रेडरिक

@ जय बाल्टर यह मायने नहीं रखता। मैं संभावित रूप से इस स्थानीय बफर की सीमा पर लिख सकता हूं और इसलिए हमेशा कुछ अन्य डेटास्ट्रक्चर को भ्रष्ट करना संभव होगा।
फ्रेडरिक

3

strlenएक से अधिक बार कॉल करने से जुड़े सुरक्षा मुद्दों से परे , किसी को आमतौर पर स्ट्रिंग के तरीकों का उपयोग नहीं करना चाहिए जिनकी लंबाई ठीक से जानी जाती है [अधिकांश स्ट्रिंग कार्यों के लिए, केवल एक बहुत ही संकीर्ण मामला है जहां उनका उपयोग किया जाना चाहिए - स्ट्रिंग्स पर जिसके लिए एक अधिकतम लंबाई की गारंटी दी जा सकती है, लेकिन सटीक लंबाई ज्ञात नहीं है]। एक बार इनपुट स्ट्रिंग की लंबाई और आउटपुट बफर की लंबाई ज्ञात होने के बाद, किसी को यह पता लगाना चाहिए कि किसी क्षेत्र को कितना बड़ा कॉपी किया जाना चाहिए और फिर memcpy()वास्तव में कॉपी में प्रदर्शन करने के लिए उपयोग करना चाहिए । यद्यपि यह संभव है कि केवल 1-3 बाइट्स की एक स्ट्रिंग की नकल करते समय strcpyयह बेहतर हो सकता memcpy()है, इसलिए कई प्लेटफ़ॉर्म पर memcpy()बड़े स्ट्रिंग्स के साथ काम करते समय दो बार से अधिक तेजी से होने की संभावना है।

हालाँकि कुछ स्थितियाँ ऐसी हैं जहाँ सुरक्षा प्रदर्शन की कीमत पर आएगी, यह एक ऐसी स्थिति है जहाँ सुरक्षित दृष्टिकोण भी तेज़ है। कुछ मामलों में, यह कोड लिखना उचित हो सकता है जो अजीब व्यवहार करने वाले इनपुट के खिलाफ सुरक्षित नहीं है, अगर इनपुट की आपूर्ति करने वाले कोड सुनिश्चित कर सकते हैं कि वे अच्छी तरह से व्यवहार किए जाएंगे, और यदि गैर-व्यवहार किए गए इनपुट के खिलाफ रख-रखाव प्रदर्शन को बाधित करेगा। यह सुनिश्चित करना कि स्ट्रिंग की लंबाई को केवल एक बार जांचा जाता है , प्रदर्शन और सुरक्षा दोनों को बेहतर बनाता है , हालांकि स्ट्रिंग की लंबाई को मैन्युअल रूप से ट्रैक करते हुए भी गार्ड सुरक्षा में मदद करने के लिए एक अतिरिक्त काम किया जा सकता है: प्रत्येक स्ट्रिंग के लिए जो एक अनुगामी नल की अपेक्षा की जाती है, अनुगामी नल को स्पष्ट रूप से लिखें स्रोत स्ट्रिंग की अपेक्षा से यह है। इस प्रकार, यदि कोई एक strdupसमकक्ष लिख रहा था :

char *strdupe(char const *src)
{
  size_t len = strlen(src);
  char *dest = malloc(len+1);
  // Calculation can't wrap if string is in valid-size memory block
  if (!dest) return (OUT_OF_MEMORY(),(char*)0); 
  // OUT_OF_MEMORY is expected to halt; the return guards if it doesn't
  memcpy(dest, src, len);      
  dest[len]=0;
  return dest;
}

ध्यान दें कि अंतिम विवरण को आम तौर पर छोड़ा जा सकता है यदि मेमसीपी ने len+1बाइट्स को संसाधित किया था , लेकिन स्रोत स्ट्रिंग को संशोधित करने के लिए एक और धागा था, जिसके परिणामस्वरूप गैर-एनयूएल-समाप्त गंतव्य स्ट्रिंग हो सकता है।


3
क्या आप कृपया एक से अधिक बार कॉल strlenकरने से जुड़े सुरक्षा मुद्दों की व्याख्या कर सकते हैं ?
बोगडान अलेक्जेंड्रू

1
@BogdanAlexandru: एक बार किसी ने कॉल किया strlenऔर वापस लौटे मूल्य के आधार पर कुछ कार्रवाई की (जो संभवतः इसे पहली जगह में कॉल करने का कारण था), तो एक दोहराया कॉल या तो (1) हमेशा पहले वाले के समान उत्तर देगा। जिस स्थिति में यह बस व्यर्थ का काम है, या (2) कभी-कभी हो सकता है (क्योंकि कुछ और - शायद एक और धागा - इस बीच स्ट्रिंग को संशोधित किया जाता है) एक अलग उत्तर देता है, उस स्थिति में कोड जो लंबाई के साथ कुछ चीजें करता है (जैसे एक बफर आवंटित करना) कोड की तुलना में एक अलग आकार मान सकता है जो अन्य चीजों (बफर की नकल) करता है।
सुपरकैट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.