UPnP Command Injection مشروح: لما الراوتر يثق بالشبكة زيادة عن اللزوم
تخيل إنك ركبت قفل ذكي على باب بيتك. القفل فيه خاصية اسمها "فتح تلقائي للأجهزة الموثوقة". أنت ما حطيت رقم سري، وما حددت مين يعتبر موثوق. القفل ببساطة يفترض إن أي واحد يقدر يكلمه على الشبكة المحلية هو من العائلة. هذا تقريباً اللي تسويه كثير من أجهزة الـ gateway المنزلية مع بروتوكول Universal Plug and Play.
UPnP انصمم عشان الكونسول أو السيرفر الإعلامي يقدر يفتح ports بدون ما تدخل على إعدادات الراوتر. البروتوكول مريح. لكنه في كثير من التطبيقات شبه خالي من المصادقة، ومستعد يقبل أوامر من أي شخص يوصل لـ SOAP control URL. لما أحد هالأوامر يكون اختبار شبكة يشغل أوامر على نظام التشغيل، ما عاد عندك ميزة مفيدة. عندك remote shell مغلفة بـ XML.
هالمقال يشرح فئة الثغرة من الصفر، يعرض سيناريو واقعي (لكن مصطنع بالكامل) للاستغلال، وينتهي بدفاعات عملية تقدر تطبقها اليوم.
وش هو UPnP Command Injection أصلاً؟
UPnP يتكون من عدة أجزاء:
- SSDP للاكتشاف (UDP multicast على 1900)
- Device description XML يسرد الـ services و control URLs
- Service Control Protocol Description (SCPD) XML يسرد الـ actions اللي الـ service تدعمها
- SOAP فوق HTTP عشان تنفذ هالـ actions فعلياً
جهاز Internet Gateway Device (IGD) العادي يعرض services مثل WANIPConnection. هالـ services غالباً فيها actions مثل AddPortMapping و GetExternalIPAddress، وفي بعض إضافات الشركات فيه أدوات تشخيص مثل RunNetworkTest أو GetPassword.
Command injection يظهر لما parameter المفروض يكون hostname أو IP ينحط مباشرة في أمر shell بدون تنظيف. النمط الكلاسيكي على الجهاز يكون كذا:
// نمط ضعيف (مبسط)
snprintf(cmd, sizeof(cmd), "ping -c 4 %s", target_host);
system(cmd);
إذا target_host يقدر يحتوي على ; أو | أو backticks أو $(...)، المهاجم يسيطر على العملية. وفي كثير من أجهزة Linux المدمجة هالعملية تشتغل كـ root.
اللفة اللي تخلي الفئة هذي خطيرة أكثر إن "المصادقة" اللي تحمي الـ action الخطير أحياناً نفسها تقدر تجيبها من خلال action ثاني في UPnP. نفس وصف الخدمة اللي يعلن عن diagnostic endpoint يعلن كمان عن GetPassword أو GetUserName يرجع مفتاح التزويد بنص واضح. بعد ما تاخذ المفتاح، تضيف HTTP header مخصص والـ diagnostic يصير shell مفتوح.
ليش يهمك الموضوع؟
- التعرض الافتراضي. كثير من راوترات المستهلكين تجي مع UPnP مفعل على جهة الـ LAN. بعضها يستمع كمان على واجهة الـ WAN لما تكون إدارة عن بعد أو ميزات معينة من مزود الخدمة مفعلة.
- ما في مصادقة حقيقية في البروتوكول الأساسي. UPnP 1.0 ما عنده access control إلزامي. فيه إضافات أمنية لاحقاً لكنها نادراً ما تطبق على الأجهزة الرخيصة.
- صلاحيات عالية. الـ UPnP daemon كثيراً يشتغل كـ root أو بصلاحيات تخليه يعدل قواعد الجدار الناري ويشغل عمليات.
- ثبات صامت. المهاجم اللي يحصل على shell يقدر يثبت reverse tunnel، يعدل الإعدادات، أو يفتح port mappings ثابتة تبقى بعد إعادة التشغيل.
- الحجم. المسوحات على مر السنين لقيت عشرات الملايين من الأجهزة ترد على SSDP من الإنترنت العام. جزء منها لسا فيه ثغرات command injection في معالجات SOAP.
الحوادث الحقيقية تثبت النقطة. في 2025–2026 نشرة من Zyxel غطت ثغرة command injection حرجة (CVE-2025-13942) توصل لها عن طريق SOAP requests مصممة. تطبيقات Realtek miniigd القديمة (CVE-2014-8361) كان فيها نفس الفئة قبل عشر سنوات. النمط ما يموت، بس ينتقل لشجرة firmware الجاية.
مثال عملي: خدمة التشخيص اللي أكثر من اللزوم مفيدة
تخيل gateway كابل منزلي عام. يطبق profile الـ InternetGatewayDevice القياسي ويضيف DiagnosticService خاصة بالشركة. وصف الجهاز يشير لـ SCPDs اثنين مهمين.
الأول (لـ WANIPConnection) يسرد الـ actions العادية بالإضافة لاثنين غير قياسيين:
-
GetUserName -
GetPassword
ولا واحد فيهم يحتاج مصادقة. استدعاء GetPassword عن طريق SOAP POST بسيط يرجع بيانات اعتماد التزويد اللي تستخدم بعدين كـ shared secret.
الثاني (DiagnosticService) يوثق action اسمه RunNetworkTest. ياخذ مدخلين:
-
TargetHost(string) -
TestType(ping / traceroute / dns)
ويرجع TestResult كـ string. تعليق في الـ XML (نعم، الشركات أحياناً تترك هذي) يقول إن الهدف يمرر مباشرة للـ shell وإن "التنظيف مؤجل لإصدار مستقبلي".
الاكتشاف
-
تجيب root device description (غالباً
/rootDesc.xmlأو اللي LOCATION header من SSDP أعلن عنه). -
تتبع روابط
SCPDURLلكل service. - تلاحظ الـ diagnostic action و action استرجاع كلمة المرور.
- ترسل طلب GetPassword SOAP. الرد فيه المفتاح.
-
ترسل طلب RunNetworkTest مع المفتاح في header مخصص (
X-Diag-Keyفي المثال) وTargetHostخبيث.
payload استغلال بسيط يكون كذا:
<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:RunNetworkTest xmlns:u="urn:schemas-upnp-org:service:DiagnosticService:1">
<TargetHost>127.0.0.1; id</TargetHost>
<TestType>ping</TestType>
</u:RunNetworkTest>
</s:Body>
</s:Envelope>
الـ control URL يرجع مخرجات id (أو أي أمر تختاره) داخل رد SOAP. من هنا المهاجم يقدر يقرأ ملفات الإعدادات، يسحب credentials، أو يفتح reverse shell.
نفس التقنية تشتغل مع أي من فواصل الـ injection الكلاسيكية. بعض التطبيقات ترشح المسافات أو أحرف معينة، غيرها ما ترشح شيء. لما يكون فيه ترشيح عادة يكون ناقص، فـ $(id) أو حيل الـ newline لسا تنجح.
المتغيرات المجاورة
- الـ parameter القابل للحقن ممكن يكون رقم port أو مدة lease أو string وصف بدل host.
- الـ header "المصادقة" ممكن ما يكون موجود أصلاً؛ بعض الأجهزة تثق بأي عميل على الـ LAN.
- نفس فئة الثغرة تظهر في تحليل SSDP (command injection عن طريق ST header) وفي معالجة event subscription.
أمثلة كود ضعيفة
C (أسلوب الأجهزة المدمجة)
/* ضعيف */
void run_network_test(const char *host, const char *type) {
char cmd[256];
if (strcmp(type, "ping") == 0) {
snprintf(cmd, sizeof(cmd), "ping -c 3 %s 2>&1", host);
} else if (strcmp(type, "traceroute") == 0) {
snprintf(cmd, sizeof(cmd), "traceroute %s 2>&1", host);
}
FILE *fp = popen(cmd, "r");
// ... اقرأ المخرجات وارجعها في رد SOAP
}
/* host ما يتحقق منه أبداً؛ أي shell metacharacter يشتغل */
/* مصلح */
#include <ctype.h>
#include <stdbool.h>
bool is_safe_host(const char *host) {
if (!host || !*host) return false;
for (const char *p = host; *p; p++) {
if (!(isalnum((unsigned char)*p) || *p == '.' || *p == '-' || *p == ':'))
return false; /* ارفض أي شيء مو IP أو hostname معقول */
}
return true;
}
void run_network_test(const char *host, const char *type) {
if (!is_safe_host(host)) {
/* ارجع خطأ UPnP 402 أو 701 */
return;
}
/* أفضل: استخدم execve مع مصفوفة arguments، أبداً ما تستخدم shell */
char *argv[] = {"ping", "-c", "3", (char *)host, NULL};
/* ... */
}
Python (نموذج أولي / gateway أعلى مستوى)
# ضعيف
import subprocess
def run_test(host: str, test_type: str) -> str:
if test_type == "ping":
cmd = f"ping -c 3 {host}"
else:
cmd = f"traceroute {host}"
return subprocess.getoutput(cmd) # shell=True من تحت
# مصلح
import subprocess
import ipaddress
import re
HOST_RE = re.compile(r"^[A-Za-z0-9]([A-Za-z0-9\-\.]*[A-Za-z0-9])?$")
def run_test(host: str, test_type: str) -> str:
try:
ipaddress.ip_address(host) # اقبل IPs نقية
except ValueError:
if not HOST_RE.match(host) or len(host) > 253:
raise ValueError("invalid host")
if test_type == "ping":
argv = ["ping", "-c", "3", host]
else:
argv = ["traceroute", host]
return subprocess.check_output(argv, text=True, timeout=30)
التغييرات الأساسية نفسها في أي لغة: أبداً ما تمرر بيانات يتحكم فيها المهاجم لـ shell، وأبداً ما تثق بـ UPnP action يرجع credentials بدون مصادقة قوية خاصة فيه.
الدفاع / كيف تصلح
- عطل UPnP على جهة الـ WAN. إذا الميزة مطلوبة فقط لأجهزة الـ LAN، اربط الـ daemon على الواجهة الداخلية فقط.
- اطفِ UPnP بالكامل لما ما تحتاجه. أغلب مستخدمي البيوت ما يلاحظون الفرق. البيئات المؤسسية يفضلون قواعد port-forward صريحة أو VPN مناسب.
- احذف أو قيد الـ diagnostic actions. ميزات اختبار الشبكة اللي تشغل shell ما لازم تكون موجودة في firmware الإنتاج، أو لازم تتطلب credential قوي غير قابل للاسترجاع وتشتغل تحت مستخدم مقيد جداً.
- أبداً ما تعرض actions من نوع GetPassword. الـ credentials المستخدمة لتزويد مزود الخدمة أو استعادة الأدمن لازم تكون خلف مصادقة صحيحة، مو داخل استدعاء SOAP بدون مصادقة.
- نظف وفضل واجهات من نوع execve. حتى لو أبقيت diagnostic endpoint، مرر الـ arguments كمصفوفة. أبداً ما تبني command string.
- حدث الـ firmware. نفس فئة الثغرة انصلحت وانرجعت عبر عدة شركات لأكثر من عقد. تابع النشرات لطرازات أجهزتك.
- راقب port mappings غير متوقعة. الأدوات اللي تسحب جدول UPnP NAT بشكل دوري تقدر تكشف مهاجم يستخدم فقط AddPortMapping الشرعي للثبات.
أفكار ختامية
UPnP حل مشكلة سهولة استخدام حقيقية. لكنه كمان خلق سطح هجوم دائم ومنخفض الاحتكاك الشركات تستهين فيه باستمرار. لما أداة تشخيص كانت مخصصة لفنيي الدعم تنتهي بقبول shell metacharacters وتحمي نفسها بكلمة مرور action ثاني في UPnP يرجعها بكل سرور، التصميم فشل في كل طبقة.
أكثر خدمة UPnP أماناً هي اللي مو شغالة. ثاني أكثر أماناً هي اللي أبداً ما تلصق إدخال المستخدم في shell. أي شيء ثاني بس ينتظر الـ scanner الجاي يلقاه.
المراجع
- نشرة أمان Zyxel تغطي CVE-2025-13942 (UPnP command injection يؤدي لـ RCE)
- CVE-2014-8361 – تنفيذ أوامر Realtek SDK miniigd UPnP SOAP
- بحث Rapid7: "Security Flaws in Universal Plug and Play: Unplug, Don't Play" (2013)
- بحث Akamai عن حملات UPnProxy / NAT injection
- المركز الكندي للأمن السيبراني: Universal Plug and Play (ITSAP.00.008)
- CallStranger (CVE-2020-12695) والقضايا المتعلقة بتضخيم UPnP / تسريب البيانات