curl में heap overflow कैसे बना
(daniel.haxx.se)- curl 8.4.0 की रिलीज़ के साथ सामने आया CVE-2023-38545 SOCKS5 proxy handling के दौरान हुआ heap buffer overflow है, और curl security issues में दुर्लभ HIGH severity vulnerability है
- समस्या 2020 में SOCKS5 connection code को blocking call से non-blocking state machine में बदलने के दौरान आई, और curl 7.69.0 से प्रभावित करती है
- remote name resolution mode में 255 bytes से बड़े hostnames को local resolution में बदलने वाला पुराना logic, state machine की re-call के साथ मिलकर, बहुत लंबा hostname छोटे buffer में copy कर सकता था
- attack सफल होने के लिए libcurl client को SOCKS5 proxy-resolver-mode और automatic redirects का उपयोग करना होगा, और attacker-controlled HTTPS server को 16KB से बड़े और 64KB या कम hostname वाला HTTP 30x
Location:लौटाना होगा - curl 8.4.0 से बहुत लंबे hostnames पर remote resolution को local resolution में बदलने के बजाय error लौटाया जाता है, और इसी scenario को रोकने वाला test जोड़ा गया है
CVE-2023-38545 और curl 8.4.0
- curl 8.4.0 release के साथ CVE-2023-38545 security advisory और details सार्वजनिक किए गए
- इस issue को curl में लंबे समय बाद आया सबसे गंभीर security issue माना गया, और severity HIGH तय की गई
- मुख्य flaw SOCKS5 proxy connection handling के दौरान खास conditions में होने वाला heap buffer overflow है
SOCKS5 और name resolution के तरीके
- curl August 2002 से SOCKS5 support करता है
- SOCKS5 एक proxy protocol है जो बीच के server के जरिए network communication setup करता है
- इसे Tor के जरिए communication setup करने के लिए इस्तेमाल किया जा सकता है
- संगठन या company के अंदर से internet access करते समय भी इसका इस्तेमाल हो सकता है
- SOCKS5 में hostname resolution के दो तरीके हैं
- client hostname को locally resolve करने के बाद resolved address proxy को देता है
- client पूरा hostname proxy को भेजता है और proxy remotely resolve करता है
2020 का बदलाव जिससे vulnerability आई
- 2020 की शुरुआत में SOCKS5 proxy से connect करने वाला function blocking call से non-blocking state machine में बदला गया
- यह बदलाव 14 February 2020 को master में गया, और curl 7.69.0 में शामिल हुआ
- curl 7.69.0 इस improvement वाली पहली release थी और नतीजतन CVE-2023-38545 के लिए vulnerable पहली release भी बनी
- बदलाव का उद्देश्य यह था कि जब कई parallel transfers सभी SOCKS5 से गुजरें, तो अधिक दिखाई देने वाला improvement मिले
state machine में टूटा resolution mode
- state machine function connection स्थापित होने तक हर बार अधिक network data आने पर बार-बार call होता है
- function के top पर
socks5_resolve_locallocal variable यह बताता है कि curl hostname को खुद resolve करेगा या name proxy को भेजेगा - यह variable हर बार state machine चलने पर function call की शुरुआत में proxy mode के आधार पर फिर से set होता है
- INIT state की condition ने समस्या बनाई
- SOCKS5 का hostname field अधिकतम 255 bytes तक ही allow करता है
- अगर hostname 255 bytes से बड़ा हो, तो SOCKS5 proxy उसे resolve नहीं कर सकता
- पुराने curl code में remote resolution mode में बहुत लंबा hostname मिलने पर
socks5_resolve_localकोTRUEकर के local resolution mode में switch किया जाता था
- अगर user ने remote resolution request किया था, तो curl को mode बदलने के बजाय fail होना चाहिए था, लेकिन बहुत पहले जोड़ा गया switch behavior वैसे ही बना रहा
heap overflow होने का flow
- अगर SOCKS5 server पर्याप्त तेज़ न हो और state machine को आगे बढ़ने के लिए network data न मिले, तो function return हो जाता है
- बाद में data मिलने पर वही state machine function फिर call होता है
- re-call के समय
socks5_resolve_localfunction के top पर proxy mode के आधार पर फिर set होता है- previous call में बहुत लंबे hostname की वजह से
TRUEहुआ value कायम नहीं रहता - value फिर से proxy को name remotely resolve करना चाहिए वाली state में आ जाता है
- previous call में बहुत लंबे hostname की वजह से
- curl proxy को भेजने के लिए protocol frame memory buffer में बनाता है और destination information copy करता है
- गलत state value की वजह से अगर बहुत लंबे hostname को जस का तस भेजने की कोशिश होती है, तो allocated target buffer से आगे जाकर पास की heap memory तक overwrite हो सकती है
buffer size और hostname constraints
- curl proxy को भेजने के लिए protocol frame बनाते समय normal download buffer को reuse करता है
- download buffer size की conditions ये हैं
- default value 16KB है
- curl tool buffer size को 100KB पर set करता है
- application request के अनुसार अलग size इस्तेमाल हो सकता है
- allowed minimum size 1024 bytes है
- अगर buffer size 65541 bytes से छोटा हो, तो यह overflow संभव है
- buffer जितना छोटा होगा, संभावित overflow size उतना बड़ा होगा
- URL के hostname पर practical size limit नहीं है, लेकिन libcurl URL parser 65535 bytes से बड़े names को reject करता है
- DNS अधिकतम 253 bytes तक के hostnames ही allow करता है
- 253 bytes से बड़े legal names दुर्लभ हैं, और 1024 bytes से बड़े real names practically देखने को नहीं मिलते, इसलिए attack के लिए जानबूझकर बेहद लंबा hostname चाहिए
- URL के hostname field में केवल कुछ octets ही आ सकते हैं, और invalid byte values को URL parser reject कर देता है
- अगर libcurl IDN library इस्तेमाल करने के लिए build किया गया हो, तो वह library भी invalid hostnames को reject कर सकती है
attack सफल होने की शर्तें
- attacker को ऐसा HTTPS server control करना होगा जिस तक libcurl इस्तेमाल करने वाला client SOCKS5 proxy के जरिए proxy-resolver-mode में access करता है
- attack server HTTP 30x response के रूप में crafted redirect लौटा सकता है
- redirect के
Location:header में इस form का बहुत लंबा hostname हो सकता हैLocation: https://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/- hostname length 16KB से अधिक से 64KB तक
- अगर libcurl client में automatic redirect following on हो, और SOCKS5 proxy इस local variable issue को trigger करने के लिए “काफी slow” हो, तो crafted hostname बहुत छोटे buffer में copy हो जाता है
- ये conditions पूरी होने पर पास की heap memory तक write होता है और heap buffer overflow बनता है
fix और bug bounty
- curl 8.4.0 से बहुत लंबे hostname की वजह से remote resolution से local resolution में mode बदलने के बजाय error return होता है
- इसी scenario के लिए dedicated test case भी जोड़ा गया
- इस issue को Jay Satiro ने report, analyze और patch किया
- इस vulnerability के लिए अब तक curl में दी गई सबसे बड़ी bug bounty दी गई
- reporter को 4,660 dollars
- IBB policy के तहत curl project को 1,165 dollars
C और memory-safe languages
- अगर curl C के बजाय memory-safe language में लिखा गया होता, तो इस category के flaws नहीं होते
- लेकिन curl को किसी दूसरी language में port करना फिलहाल agenda में नहीं है
- practical approach दो बातों तक सीमित होती है
- memory-safe languages में लिखी dependencies को अधिक allow, use और support करना
- hyper adoption की तरह curl के हिस्सों को gradually replace करना
- ऐसी development अभी बहुत धीमी चल रही है, और संबंधित difficulties भी साफ दिखती हैं
- curl निकट भविष्य में भी C में लिखा हुआ ही रहेगा
- curl 8.4.0 में report किए गए latest 2 CVEs को मिलाकर, अब तक curl में पाई गई security vulnerabilities में से 41% शायद memory-safe language इस्तेमाल करने पर नहीं होतीं
- हालांकि C-related issues के शुरुआती लगभग 80% जब introduce हुए थे, उस समय Rust इस उद्देश्य के लिए practical choice नहीं था
1315 दिन बाद मिली flaw
- यह flaw code में 1315 दिन तक रहा
- बेहतर test set होता तो इसे पकड़ा जा सकता था
- curl कई static code analyzers बार-बार चलाता है, लेकिन इस function की समस्या किसी analyzer ने नहीं पकड़ी
- 20 billion से ज्यादा installations में गए code में heap overflow ship करने का अनुभव recommend करने लायक नहीं था
- public disclosure से पहले की report और handling process HackerOne report में देखी जा सकती है
1 टिप्पणियां
Hacker News की राय
यह अब भी हैरान करने वाला है कि इतने सारे डिवाइस असल में एक ऐसी library पर निर्भर हैं जिसका ज़्यादातर हिस्सा एक ही व्यक्ति ने लिखा है। बोझ बहुत बड़ा रहा होगा, और नीचे के वाक्य से भी यह साफ दिखता है
“अब code पढ़ता हूँ तो bug न दिखना असंभव है। यह स्वीकार करना सचमुच तकलीफदेह है कि मैंने यह गलती नहीं पकड़ी, और यह खामी 1315 दिनों तक code में बिना पकड़े रही। माफ़ी चाहता हूँ। मैं आखिर इंसान ही हूँ।”
अगर Daniel यह देख रहे हों, तो मुझे लगता है कि इतनी मेहनत के लिए धन्यवाद, और माफ़ी मांगने की बिल्कुल ज़रूरत नहीं है। आखिर source सभी के पढ़ने और review करने के लिए खुला था
https://www.buzzfeed.com/chrisstokelwalker/the-internet-is-b...
“यह report पूरी तरह सही लगती है, और दिल से तकलीफ देती है।”
blog में लिखा “2 अरब से ज़्यादा installations में जाने वाले code में heap overflow deploy करने का अनुभव मैं recommend नहीं करूँगा” भी ऐसा ही है; इतनी बड़ी ज़िम्मेदारी को इतने कम reward के साथ उठाना कठोर है
निजी तौर पर मैं अक्सर सोचता रहा हूँ कि चाहे मैं कितना भी focus करने की कोशिश करूँ, कितनी चीज़ें छूट जाती हैं। code review जैसी गतिविधियाँ इस पहलू—यानी attention training या समग्र और contextual awareness बढ़ाने—के लिए अच्छा तरीका लगती हैं। उदाहरण के लिए, एक जगह जहाँ मैं कई सालों से अक्सर गुजरता था, वहाँ किसी ने अच्छी तरह सँवारी हुई फूलों की क्यारी बनाई थी; पहली बार ध्यान में आने के बाद करीब 1 साल तक मैं रुककर उसे देखा करता था, लेकिन कुछ महीने पहले पहली बार पता चला कि उसके ठीक कुछ फीट बगल में जोड़ी जैसी एक और क्यारी भी है। बहुत संभावना है कि वह क्यारी शुरू से ही रही हो, लेकिन उसके अस्तित्व का ही पता नहीं था, इसलिए पक्का नहीं कह सकता
इसे पूरी मानवता के ज्ञान तक फैलाकर सोचें, तो कभी-कभी लगता है कि कोई एक ही व्यक्ति किसी चीज़ को नोटिस करता है, और वह observation व्यवहार बदलते हुए हम सभी तक फैलना शुरू हो जाता है। कभी-कभी कई कोशिशें और लंबा समय चाहिए होता है। यह भी सोचता हूँ कि ऐसे कितने low-hanging fruit होंगे जिन्हें अभी तक किसी ने नोटिस नहीं किया, और जो लोग पहले से ध्यान देते आए हैं उनके बनाए सरल और बुनियादी best practices को integrate करने भर से भी सभी का न्यूनतम स्तर ऊपर उठ सकता है
Julia Evans का https://jvns.ca/blog/2023/10/06/new-talk--making-hard-things... और Dan Luu का https://danluu.com/p95-skill/ भी इसी से जुड़ते हैं। निष्कर्ष में, Stenberg और curl, और internet infrastructure के वे कई हिस्से बनाने वाले लोगों के प्रति बहुत आभार महसूस होता है जिन्हें हम रोज़ स्वाभाविक मानकर इस्तेमाल करते हैं
कभी-कभी “अगर टूटा नहीं है तो ठीक मत करो” वाला सिद्धांत “टूटने तक support मत करो” के रूप में समझ लिया जाता है, जिससे काफी अप्रिय surprises मिलते हैं
sustainability की समस्या हो सकती है, लेकिन ऐसे projects आगे भी बने रहेंगे, और Daniel की तरह सिर्फ अपने लिए नहीं बल्कि community के लिए काम करने वाले सभी लोगों को मेरा सम्मान
memory safety और memory-safe languages पर निष्कर्ष बहुत उचित है। सार यह है
अगर curl C की जगह किसी memory-safe language में लिखा गया होता, तो इस तरह की defects की पूरी category असंभव होती
उस दिशा में जो तरीका व्यवहार्य और उचित लगता है, वह है memory-safe languages में लिखी dependencies को अधिक allow, use और support करना, और hyper अपनाने की तरह curl के हिस्सों को धीरे-धीरे टुकड़ों में replace करना
लेकिन ऐसा development फिलहाल लगभग glacier जैसी धीमी गति से चल रहा है, और इससे जुड़ी कठिनाइयों को दर्दनाक रूप से साफ दिखाता है। निकट भविष्य में curl C में ही रहेगा। जिसे यह पसंद नहीं, वह खुद sleeves चढ़ाकर काम कर सकता है
curl 8.4.0 में report किए गए नवीनतम दो CVE तक शामिल करें, तो अब तक curl में मिले security vulnerabilities के cumulative total में से 41% शायद memory-safe language इस्तेमाल करने पर पैदा ही नहीं होते। लेकिन Rust उस समय इस उपयोग के लिए व्यावहारिक विकल्प नहीं था, जब C से जुड़ी समस्याओं के शुरुआती करीब 80% introduce हो रहे थे
कई static code analyzers बार-बार चलाए गए, लेकिन इस function में कोई समस्या नहीं पकड़ पाए
बेहतरीन analysis post है। हालांकि CVE तक पढ़ने के बाद भी यह अभी स्पष्ट नहीं है कि किन परिस्थितियों में असर पड़ेगा। मेरी समझ के मुताबिक, नीचे दी शर्तें पूरी होने पर असर पड़ता है
SOCKS5 proxy इस्तेमाल हो, उस proxy से host name resolve कराया जा रहा हो, buffer size default 100KB से बदलकर 65541 bytes से कम कर दिया गया हो, और SOCKS5 proxy request को तुरंत process करने के लिए बहुत slow हो
सुधार: buffer से जुड़ा विवरण सही नहीं है। libcurl download buffer को reuse करता है और default 16KB है, लेकिन कहा गया है कि curl खुद
--limit-rateका इस्तेमाल न करने तक इसे manually 100KB पर set करता है। delay वाली शर्त भी गलत थी। CVE के मुताबिक, सामान्य server delay भी इस bug को trigger करने के लिए पर्याप्त “slow” होने की अच्छी संभावना है, और attacker को denial of service या SOCKS server control के ज़रिए असर डालने की जरूरत नहीं हैइसलिए attack path बहुत narrow लगता है; सोच रहा हूं कि क्या मैं कुछ miss कर रहा हूं
root@1aac5e228e16:/build/curl-7.74.0# curl -vvv -x socks5h://host.docker.internal:9050 $(python3 -c "print(('A'10000), end='')")Trying 192.168.65.254:9050...* SOCKS5: server resolving disabled for hostnames of length > 255 [actual len=10000]* SOCKS5 connect to AAAAA...* Send failure: Bad file descriptor* Failed to send SOCKS5 connect request.Segmentation faulthttps://gist.github.com/xen0bit/0dccb11605abbeb6021963e2b1a8...
docker-proxyनाम से shell scripts का एक bundle बनाया है, जो openconnect VPN चलाने वाले Docker container के जरिए SOCKS5 tunnel बनाता है। यह तब काम आता है जब कई customers के अलग-अलग VPN संभालने हों और हर VPN machine का सारा traffic अपनी तरफ भेजने की मांग करेhttps://github.com/carlosonunez/docker-proxy
इसे इस तरह design किया गया है कि DNS resolution remote पर हो, इसलिए यह CVE यहां सीधे लागू होता है
अब तक पढ़े गए CVE analysis posts में इसे सबसे अच्छा कहा जा सकता है। हमारे दौर के core software में से एक बनाने वाले व्यक्ति होने के बावजूद, पूरे लेख में उनका विनम्र रवैया भी तारीफ के काबिल है। सच में बहुत सम्माननीय
if(!socks5_resolve_local && hostname_len > 255) {socks5_resolve_local = TRUE;}यह सच में बहुत खराब idea है। censorship bypass tools से अपनी privacy बचाने वाले लोगों के लिए DNS के जरिए identity leak हो सकती है
लेखक भी यही सोचते हैं
https://hackerone.com/reports/2187833
कहा गया है कि “अगर curl C के बजाय memory-safe language में लिखा गया होता, तो इस तरह की flaws असंभव होतीं”, लेकिन बस इतना कहा जा सकता है कि शायद असंभव होतीं। हमें हमेशा नहीं पता होता कि language की virtual machine barrier से बाहर निकलने का तरीका है या नहीं। हालांकि लेखक ने RFC1123 में बताई गई DNS host name limit को स्पष्ट रूप से ignore किया, और यह limit Java libraries में भी hardcoded है
https://www.rfc-editor.org/rfc/rfc1123
“URL के host name पर कोई practical size limit नहीं है, लेकिन libcurl का URL parser 65535 bytes से लंबे names को reject करता है। DNS अधिकतम 253 bytes वाले host names ही allow करता है। इसलिए 253 bytes से लंबे legal names दुर्लभ हैं। 1024 bytes से लंबे वास्तविक names के बारे में तो लगभग सुना ही नहीं गया।”
आज DNS 99.9% मामलों में इस्तेमाल होता है, लेकिन यह host name को address में resolve करने वाला एकमात्र mechanism नहीं है
RFC host name size की upper limit कहां तय करता है, यह नहीं मिला
इसका मतलब यह भी हो सकता है कि compiler यह prove करने की कोशिश करता है कि memory को bounds के बाहर access नहीं किया जा रहा, clear ownership के बिना access नहीं किया जा रहा, और base object की lifetime के भीतर ही access किया जा रहा है। उदाहरण के लिए Rust ऐसा करता है
बेशक compiler महत्वपूर्ण जगहों पर internal failure checks और guards भी जोड़ सकता है। Rust ऐसा नहीं करता, लेकिन उन systems में यह अच्छा हो सकता है जहां register के अंदर bit flip जैसी चीजों की चिंता करनी पड़े। जैसे X-ray scanners जैसे high-radiation environments, जहां ECC memory से पकड़े नहीं जाने वाले cases हो सकते हैं
exploit करने के लिए बेहद खास conditions चाहिए होने के बावजूद इसमें बढ़ा-चढ़ाकर पेश करने और drama की मात्रा काफी ज़्यादा है
कल आए Windows 10 security update में नया curl version शामिल नहीं था। Windows में शामिल curl अभी भी 8.0.1 है
लंबा और दिलचस्प लेख है, लेकिन इसे इस तरह संक्षेप में कहा जा सकता है: “इसलिए system libraries को safe languages में लिखा जाना चाहिए या उनकी correctness prove होनी चाहिए”
अगर हमारे समय के सबसे अच्छे, सबसे दयालु और पारदर्शी C programmers में से एक ऐसा लेख लिख रहा है, तो हमें ध्यान देना चाहिए