stihl не предоставил(а) никакой дополнительной информации.
While attempting to compare the security level of various VPN vendors, I kept falling down the path of searching for vulnerabilities instead. This blog post details the ones I discovered in SonicWall’s SMA 500, which were patched in December 2024. This post has been delayed to coincide with my talk at Для просмотра ссылки Войди или Зарегистрируйся on this exact subject.
Vulnerabilities in commercial-grade SSL VPN devices have been commonplace in the last few years, which brought me to investigating whether any of them were better equipped to protect users than others. While this will be the subject of another post, I kept getting distracted and actually searched for vulnerabilities instead of completing the initial goal of the project.
One of the targets I was researching was SonicWall’s SMA 500. It felt like it was a little less popular than other vendors and as such also seemed to be less frequently affected by new vulnerabilities. However, the general look of the code suggested that vulnerabilities were probably lying in wait somewhere.
I was able to get my hands on a trial VM directly from Для просмотра ссылки Войдиили Зарегистрируйся‘s website, which is always helpful. All information detailed below relates to version 10.2.1.13-72sv of SonicWall’s SMA, which was the latest version at the time.
After booting up the device, a network scan showed that only ports 80 and 443 were open, while the console displayed a CLI, but no direct way of getting a shell.
Для просмотра ссылки Войдиили Зарегистрируйся
SonicWall’s Web interface
Для просмотра ссылки Войдиили Зарегистрируйся
SonicWall’s command line interface
My first objective, as in most of these projects was to execute arbitrary commands on the device, as this would allow to extract the file system and also debug the running appliance. In order to do this, I used an old trick one of my former colleagues showed me. It works something like this:
Для просмотра ссылки Войдиили Зарегистрируйся
First goal achieved
With this access, I then analysed the system from the inside, looking for any service that might be listening for network connections.
Only two processes seemed to accept connections, one of them being Apache, while the other is a Flask API which is run with the following command:
python3.6 /usr/src/EasyAccess/www/python/authentication_api/restful_api.py
Since only Apache accepted remote connections, I spent some time analysing its configuration and ended up with the following understanding of how it was set up.
Для просмотра ссылки Войдиили Зарегистрируйся
Apache simplified overview
While looking through Apache’s configuration, I discovered that it was vulnerable to the path confusion issue which was presented by Orange Tsai at Blackhat last year. I won’t repeat what has already been explained, so if you don’t know what I’m talking about, you can get all the information Для просмотра ссылки Войдиили Зарегистрируйся. But essentially, in certain cases, it is possible to trick Apache into displaying files located outside of the web root.
The Apache version and configuration file in SonicWall’s SMA had all the requirements to exploit the vulnerability. Specifically, the following configuration line is the one that could be exploited:
Verifying whether the vulnerability is present can be done by issuing the two following requests and checking that the contents are identical.
One particularly interesting file is the SQLite database located at /tmp/temp.db as it contains the session identifiers of any logged in user, which means that it is possible to compromise the account of any user by simply waiting for them to log in.
или Зарегистрируйся
Downloading the SQLite database
While this is probably the most impactful of the findings, this was not the primary goal of my research, as I was mostly searching for memory corruption protections and vulnerabilities. This drove me to investigate the various CGIs exposed by the Web server. Using Для просмотра ссылки Войдиили Зарегистрируйся showed that while some protections are enabled, PIE is notably absent, and many binaries were not fortified.
Для просмотра ссылки Войдиили Зарегистрируйся
Checksec output on some of the CGI files
My initial research was centered around identifying the use of potentially dangerous functions, such as strcpy, sprintf and others. I wrote a script based on the Ghidra API to highlight any occurrences of these functions in a binary. I then combined this with a second script that identifies links between binaries to get a better picture of what is used where in a system. They can both be found Для просмотра ссылки Войдиили Зарегистрируйся.
This led to the discovery of numerous memory corruption issues, such as the one displayed below, taken from the sonicfiles CGI, where a user input of up to 0x400 bytes is copied into a memory location on the heap of only 0x80 bytes.
That is a straightforward heap overflow if I’ve ever seen one, but heap exploitation is not my forte, so I’ll let others come up with ways of exploiting it. It is also only accessible after authentication, so I didn’t spend too much time on it. I simultaneously found many stack-based buffer overflows, one of which could be accessed without requiring authentication in the cifsnavigate CGI.
In this case, the input buffer is passed to an encoding function which can quintuple the length of the buffer as shown below (code from libSys.so).
It is expected that the calling function verifies that the destination buffer is sufficiently large to hold the result, but obviously this isn’t done all the time.
From an exploitation perspective, bypassing the stack canary protection would be required to gain code execution. Brute-forcing its value would be possible, since it’s only 32 bits (yes…) and one byte is a null byte. However, this second fact is trickier to get around, since all the stack-based memory corruptions I found used string functions such as strcpy which stop at the first null byte. We’d need multiple overwrites to be able to overwrite the canary and then place a null byte in the correct location. Something like what’s shown below could do the trick, but since it requires admin access already, I did not pursue this avenue any further.
Further research uncovered another memory corruption which occurs when the server processes an NTLM response header from a backend server. In this case, the apr_base64_decode function is used, which does allow to write arbitrary null bytes. Again, the calling function should check that the destination buffer is large enough to hold the decoded buffer, but this is not the case in this particular function, as can be seen below in the code taken from mod_httprp.so.
Having generated multiple crashes throughout my research, I eventually discovered that the Apache log file conveniently logs crash details, including a stack trace with various memory locations.
Для просмотра ссылки Войдиили ЗарегистрируйсяRecovering the Apache log file
As shown here, the log file can be recovered through the path confusion vulnerability described above, which allows for following exploit path to be used:
или Зарегистрируйся. I’m pretty sure there are easier ways of getting code execution, maybe through some of the heap overflows, but I thought this one ended up being quite interesting.
While searching for memory corruption issues was my primary focus, I did spend a little bit of time analysing authentication to the device (before discovering I could pretty much bypass it with the path confusion attack). This led to the discovery of two separate vulnerabilities linked with Multi-Factor Authentication. While they don’t allow to fully bypass authentication, as they still require a valid password, I think they are worth mentioning.
The first is found in the functionality used to generate backup codes when one time passwords are used. Part of the code is displayed here.
As can be seen at the top, srand is used to initialise the pseudo-random number generator with… wait for it… the current time! This is obviously terrible, since if the time of a response can be determined, the exact backup codes can be derived. What makes this even worse is the fact that the request to retrieve the backup codes is vulnerable to Cross-Site Request Forgery and could therefore be triggered by someone visiting a malicious link, at which point the attacker could know when the request and response are generated.
The second issue linked to MFA is related to the way the appliance handles certificate-based authentication. When this is enabled, Apache is responsible for verifying the certificate provided by the user and then transfers it to the back-end Flask API in the form of environment variables.
Для просмотра ссылки Войдиили Зарегистрируйся
Simple representation of how certificate data is processed
The code responsible for retrieving those values in the API is shown below.
The big issue here is that since Flask does not specify where these parameters have to be taken from (i.e. the environment variables), it is possible to just send them along as POST parameters in a login request and therefore completely bypass the certificate validation process.
U1VDQ0VTUw== in this request is just the base64-encoded version of SUCCESS.
After discovering these issues, I reported them to SonicWall who had them patched within 6 weeks or so, you can see the full timeline below.
или Зарегистрируйся, where it took over 5 months to issue a fix. And for reporting these issues, I was rewarded with a thank you message and the following CVEs:
Для просмотра ссылки Войдиили Зарегистрируйся
Vulnerabilities in commercial-grade SSL VPN devices have been commonplace in the last few years, which brought me to investigating whether any of them were better equipped to protect users than others. While this will be the subject of another post, I kept getting distracted and actually searched for vulnerabilities instead of completing the initial goal of the project.
One of the targets I was researching was SonicWall’s SMA 500. It felt like it was a little less popular than other vendors and as such also seemed to be less frequently affected by new vulnerabilities. However, the general look of the code suggested that vulnerabilities were probably lying in wait somewhere.
I was able to get my hands on a trial VM directly from Для просмотра ссылки Войди
After booting up the device, a network scan showed that only ports 80 and 443 were open, while the console displayed a CLI, but no direct way of getting a shell.
Для просмотра ссылки Войди
SonicWall’s Web interface
Для просмотра ссылки Войди
SonicWall’s command line interface
My first objective, as in most of these projects was to execute arbitrary commands on the device, as this would allow to extract the file system and also debug the running appliance. In order to do this, I used an old trick one of my former colleagues showed me. It works something like this:
- Identify commands in the CLI that likely call operating system commands
- Pause the virtual machine
- Search strings linked to the OS commands in the virtual machine’s saved memory file
- Replace the expected command with another (such as bash) while maintaining the same memory size
- Resume the virtual machine
- Call the CLI command that has been “poisoned”
Для просмотра ссылки Войди
First goal achieved
With this access, I then analysed the system from the inside, looking for any service that might be listening for network connections.
Код:
bash-4.2$ $ netstat -laputen | grep LISTEN
tcp 0 0 127.0.0.1:12345 0.0.0.0:* LISTEN 0 1303574
tcp6 0 0 :::80 :::* LISTEN 0 898
tcp6 0 0 :::443 :::* LISTEN 0 902
Only two processes seemed to accept connections, one of them being Apache, while the other is a Flask API which is run with the following command:
python3.6 /usr/src/EasyAccess/www/python/authentication_api/restful_api.py
Since only Apache accepted remote connections, I spent some time analysing its configuration and ended up with the following understanding of how it was set up.
Для просмотра ссылки Войди
Apache simplified overview
While looking through Apache’s configuration, I discovered that it was vulnerable to the path confusion issue which was presented by Orange Tsai at Blackhat last year. I won’t repeat what has already been explained, so if you don’t know what I’m talking about, you can get all the information Для просмотра ссылки Войди
The Apache version and configuration file in SonicWall’s SMA had all the requirements to exploit the vulnerability. Specifically, the following configuration line is the one that could be exploited:
Код:
RewriteRule ^/(.+)\.[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[A-Za-z0-9]*-[0-9]+.*\.css$ /$1.css
Verifying whether the vulnerability is present can be done by issuing the two following requests and checking that the contents are identical.
- Для просмотра ссылки Войди
или Зарегистрируйся - Для просмотра ссылки Войди
или Зарегистрируйся
One particularly interesting file is the SQLite database located at /tmp/temp.db as it contains the session identifiers of any logged in user, which means that it is possible to compromise the account of any user by simply waiting for them to log in.
- Для просмотра ссылки Войди
или Зарегистрируйся
Downloading the SQLite database
While this is probably the most impactful of the findings, this was not the primary goal of my research, as I was mostly searching for memory corruption protections and vulnerabilities. This drove me to investigate the various CGIs exposed by the Web server. Using Для просмотра ссылки Войди
Для просмотра ссылки Войди
Checksec output on some of the CGI files
My initial research was centered around identifying the use of potentially dangerous functions, such as strcpy, sprintf and others. I wrote a script based on the Ghidra API to highlight any occurrences of these functions in a binary. I then combined this with a second script that identifies links between binaries to get a better picture of what is used where in a system. They can both be found Для просмотра ссылки Войди
This led to the discovery of numerous memory corruption issues, such as the one displayed below, taken from the sonicfiles CGI, where a user input of up to 0x400 bytes is copied into a memory location on the heap of only 0x80 bytes.
Код:
pcVar3 = (char *)MEM_MALLOC(0x80); // Allocate buffer of 0x80 bytes
Arg1 = (char *)malloc(0x400);
m_overflowed = (char *)MEM_MALLOC(0x180);
pvVar4 = malloc(0xffff);
if (pvVar4 == (void *)0x0) {
iVar5 = -1;
iVar15 = local_54;
goto LAB_08052cd7;
}
iVar5 = gcgiFetchString("Arg1",Arg1,0x400); // Get up to 0x400 bytes from the Arg1 parameter
bVar18 = iVar5 == 0;
iVar5 = -1;
iVar15 = local_54;
if (!bVar18) goto LAB_08052cd7;
iVar5 = 3;
pcVar11 = Arg1;
m_pwned2 = "ftp";
do {
if (iVar5 == 0) break;
iVar5 = iVar5 + -1;
bVar18 = *pcVar11 == *m_pwned2;
pcVar11 = pcVar11 + (uint)bVar19 * -2 + 1;
m_pwned2 = m_pwned2 + (uint)bVar19 * -2 + 1;
} while (bVar18);
if (!bVar18) {
iVar5 = 4;
pcVar11 = Arg1;
m_pwned2 = "sftp";
do {
if (iVar5 == 0) break;
iVar5 = iVar5 + -1;
bVar18 = *pcVar11 == *m_pwned2;
pcVar11 = pcVar11 + (uint)bVar19 * -2 + 1;
m_pwned2 = m_pwned2 + (uint)bVar19 * -2 + 1;
} while (bVar18);
if (!bVar18) {
iVar6 = strncmp(Arg1,"smb",3); // Check that Arg1 starts with smb
iVar5 = local_54;
iVar15 = local_54;
if (iVar6 == 0) {
pcVar11 = (char *)__strdup(Arg1);
m_pwned2 = strrchr(pcVar11,0x3a);
iVar5 = 6;
if (4 < (int)m_pwned2 - (int)pcVar11) {
m_pwned2 = strchr(pcVar11,0x40);
if (-1 < (int)m_pwned2 - (int)pcVar11) {
iVar5 = ((int)m_pwned2 - (int)pcVar11) + 1;
}
}
m_pwned = pcVar11 + iVar5; // skip through some of the smb URI
m_pwned2 = strchr(m_pwned,0x2f); // find trailing slash
if ((int)m_pwned2 - (int)m_pwned < 0) { // as long as we don't just have a slash
strcpy(pcVar3,m_pwned); // copy input into small buffer
}
else {
strncpy(pcVar3,m_pwned,(int)m_pwned2 - (int)m_pwned);
}
That is a straightforward heap overflow if I’ve ever seen one, but heap exploitation is not my forte, so I’ll let others come up with ways of exploiting it. It is also only accessible after authentication, so I didn’t spend too much time on it. I simultaneously found many stack-based buffer overflows, one of which could be accessed without requiring authentication in the cifsnavigate CGI.
Код:
undefined4 main(void)
{
// [...]
undefined local_694 [1024];
undefined local_294 [640]; // locally defined buffer of 640 bytes
int canary;
bVar5 = 0;
canary = *(int *)(in_GS_OFFSET + 0x14);
gcgiSetLimits(0x100000,0);
iVar2 = initCgi();
uVar4 = 0xffffffff;
if (iVar2 < 0) goto LAB_08048a1b;
fwrite("Content-Type: Text/HTML\n\n",1,0x19,gcgiOut);
iVar2 = gcgiFetchString("cifsaddress",cifsaddress,0x400); // Get up to 0x400 bytes of the cifsaddress URL parameter
if (iVar2 == 0) {
gcgiDecodeUrlEncodedString(cifsaddress,&decodedaddress,local_1e9c);
if ((*decodedaddress == '\\') && (decodedaddress[1] == '\\')) {
initClientApi();
cspInit();
local_1e95 = '\0';
server = (char *)__strdup(decodedaddress + 2);
server = strtok(server,"\\");
decoded_share = strtok((char *)0x0,"\\");
decoded_cwd = strtok((char *)0x0,&local_1e95);
if ((decoded_share != (char *)0x0) ||
((server == (char *)0x0 || (decoded_cwd != (char *)0x0)))) {
if ((decoded_share == (char *)0x0) || (server == (char *)0x0)) goto LAB_08048a05;
if (decoded_cwd == (char *)0x0) {
gcgiEncodeUrlString(decoded_share,local_1ea4,local_1e9c);
javaScriptDoubleEscapeSpecial(local_294,decoded_share,0); // double escape special share name into small buffer
urlEncodeUnicodeString(local_294,&share,local_1e9c);
__sprintf_chk(local_694,1,0x400,"/cgi-bin/explorerlist?SERVER=%s&SHARE=%s",server,share);
}
// [...]
In this case, the input buffer is passed to an encoding function which can quintuple the length of the buffer as shown below (code from libSys.so).
Код:
void javaScriptDoubleEscapeSpecial(undefined4 *param_1,char *param_2,int param_3)
{
char cVar1;
if (param_2 == (char *)0x0) {
LAB_000fbfa8:
*(undefined *)param_1 = 0;
return;
}
cVar1 = *param_2;
joined_r0x000fbf56:
if (cVar1 != '\0') {
do {
switch(cVar1) {
case '\"':
*param_1 = 0x32353225;
*(undefined *)(param_1 + 1) = 0x32;
param_1 = (undefined4 *)((int)param_1 + 5);
break;
case '#':
*param_1 = 0x32353225;
*(undefined *)(param_1 + 1) = 0x33;
param_1 = (undefined4 *)((int)param_1 + 5);
break;
default:
*(char *)param_1 = cVar1;
param_1 = (undefined4 *)((int)param_1 + 1);
break;
case '%':
if (param_3 == 0) {
*(undefined2 *)param_1 = 0x3225;
*(undefined *)((int)param_1 + 2) = 0x35;
param_1 = (undefined4 *)((int)param_1 + 3);
}
else {
*param_1 = 0x32353225;
*(undefined *)(param_1 + 1) = 0x35;
param_1 = (undefined4 *)((int)param_1 + 5);
}
break;
case '&':
*param_1 = 0x32353225;
*(undefined *)(param_1 + 1) = 0x36;
param_1 = (undefined4 *)((int)param_1 + 5);
break;
case '\'':
*param_1 = 0x32353225;
*(undefined *)(param_1 + 1) = 0x37;
param_1 = (undefined4 *)((int)param_1 + 5);
break;
case '+':
*param_1 = 0x32353225;
*(undefined *)(param_1 + 1) = 0x42;
param_1 = (undefined4 *)((int)param_1 + 5);
break;
case '.':
*param_1 = 0x32353225;
*(undefined *)(param_1 + 1) = 0x45;
param_1 = (undefined4 *)((int)param_1 + 5);
break;
case ';':
*param_1 = 0x33353225;
*(undefined *)(param_1 + 1) = 0x42;
param_1 = (undefined4 *)((int)param_1 + 5);
break;
case '<':
*param_1 = 0x33353225;
*(undefined *)(param_1 + 1) = 0x43;
param_1 = (undefined4 *)((int)param_1 + 5);
break;
case '>':
goto code_r0x000fbf90;
}
param_2 = param_2 + 1;
cVar1 = *param_2;
if (cVar1 == '\0') break;
} while( true );
}
goto LAB_000fbfa8;
code_r0x000fbf90:
param_2 = param_2 + 1;
*param_1 = 0x33353225;
*(undefined *)(param_1 + 1) = 0x45;
cVar1 = *param_2;
param_1 = (undefined4 *)((int)param_1 + 5);
goto joined_r0x000fbf56;
}
It is expected that the calling function verifies that the destination buffer is sufficiently large to hold the result, but obviously this isn’t done all the time.
From an exploitation perspective, bypassing the stack canary protection would be required to gain code execution. Brute-forcing its value would be possible, since it’s only 32 bits (yes…) and one byte is a null byte. However, this second fact is trickier to get around, since all the stack-based memory corruptions I found used string functions such as strcpy which stop at the first null byte. We’d need multiple overwrites to be able to overwrite the canary and then place a null byte in the correct location. Something like what’s shown below could do the trick, but since it requires admin access already, I did not pursue this avenue any further.
Код:
uVar34 = domainGetDomainId(iVar4);
uVar21 = dbhGet(1);
uVar21 = domainADFindByDomainId(uVar21,uVar34);
uVar5 = domainADGetServer(uVar21);
javaScriptStrEncode(local_517,uVar5); // overwrite 1
__fprintf_chk(gcgiOut,1,"NELaunchX1.authServer = \"%s\";\n",local_517);
uVar5 = domainADGetAdRealm(uVar21);
javaScriptStrEncode(local_517,uVar5); // overwrite 2
__fprintf_chk(gcgiOut,1,"NELaunchX1.ntDomainName = \"%s\";\n",local_517);
uVar5 = sessionGetScriptPath(local_9a4);
javaScriptStrEncode(local_517,uVar5); // overwrite 3
__fprintf_chk(gcgiOut,1,"NELaunchX1.logonScript = \"%s\";\n",local_517);
domainADFree(uVar21);
Further research uncovered another memory corruption which occurs when the server processes an NTLM response header from a backend server. In this case, the apr_base64_decode function is used, which does allow to write arbitrary null bytes. Again, the calling function should check that the destination buffer is large enough to hold the decoded buffer, but this is not the case in this particular function, as can be seen below in the code taken from mod_httprp.so.
Код:
size_t httprp_ntlm_get_type3_auth(
char *param_1, char *param_2, uint *decoded_basic_sent_from_client, char *param_4
)
{
int iVar1;
char *pcVar2;
bool bVar3;
char *pcVar4;
size_t sVar5;
uint *__dest;
uint uVar6;
uint uVar7;
int iVar8;
void *__ptr;
uint *puVar9;
uint *puVar10;
int iVar11;
size_t sVar12;
int in_GS_OFFSET;
bool bVar13;
undefined local_898 [1088];
int local_458;
undefined local_454 [1076]; // static buffer size
int local_20;
undefined4 uStack_14;
sVar12 = 0;
uStack_14 = 0x46a5b;
local_20 = *(int *)(in_GS_OFFSET + 0x14);
pcVar4 = strstr(param_2,"NTLM "); // param 2 is the HTTP Authorization response header
if (pcVar4 != (char *)0x0) {
apr_base64_decode(local_454,pcVar4 + 5); // decode the header without checking the size
sVar12 = strlen((char *)decoded_basic_sent_from_client);
sVar5 = strlen(param_4);
__dest = (uint *)malloc(sVar12 + 1);
bVar13 = false;
bVar3 = false;
if (__dest != (uint *)0x0) {
pcVar4 = strchr((char *)decoded_basic_sent_from_client,0x5c);
bVar13 = true;
if (pcVar4 != (char *)0x0) {
*(undefined *)__dest = 0;
strncat((char *)__dest,pcVar4 + 1,
(int)decoded_basic_sent_from_client + ((sVar12 - 1) - (int)pcVar4));
puVar10 = __dest;
// [...]
Having generated multiple crashes throughout my research, I eventually discovered that the Apache log file conveniently logs crash details, including a stack trace with various memory locations.
Для просмотра ссылки Войди
As shown here, the log file can be recovered through the path confusion vulnerability described above, which allows for following exploit path to be used:
- Generate a crash with any memory corruption issue
- Download the /tmp/temp.db to get a valid session identifier
- Get the libc base address from the stack trace in the log file
- Prepare a ROP chain
- Spin up a fake web server which will serve the NTLM response payload on access
- Send a request to the SonicWall device telling it to connect to the fake web server
- Exploit (and bruteforce canary)
While searching for memory corruption issues was my primary focus, I did spend a little bit of time analysing authentication to the device (before discovering I could pretty much bypass it with the path confusion attack). This led to the discovery of two separate vulnerabilities linked with Multi-Factor Authentication. While they don’t allow to fully bypass authentication, as they still require a valid password, I think they are worth mentioning.
The first is found in the functionality used to generate backup codes when one time passwords are used. Part of the code is displayed here.
Код:
puVar10 = local_99;
uVar7 = time((time_t *)0x0); // Get the current time
srand(uVar7); // Init the PRNG
local_2ac = 0;
do {
iVar8 = 0;
do {
iVar2 = rand(); // Get pseudo-random value
puVar10[iVar8] = (&DAT_080495e0)[iVar2 % 0x3e];
iVar8 = iVar8 + 1;
} while (iVar8 != 8);
uVar3 = backupCode_SHA1_string(local_99 + local_2ac,8,local_49);
uVar3 = cJSON_CreateString(uVar3);
cJSON_AddItemToArray(uVar1,uVar3);
local_2ac = local_2ac + 10;
puVar10[8] = 0xd;
puVar10[9] = 10;
puVar10 = puVar10 + 10;
} while (local_2ac != 0x50);
uVar3 = dbhGet(1);
iVar8 = userFindByUserNameAndDomainName(uVar3,param_1,param_2);
As can be seen at the top, srand is used to initialise the pseudo-random number generator with… wait for it… the current time! This is obviously terrible, since if the time of a response can be determined, the exact backup codes can be derived. What makes this even worse is the fact that the request to retrieve the backup codes is vulnerable to Cross-Site Request Forgery and could therefore be triggered by someone visiting a malicious link, at which point the attacker could know when the request and response are generated.
The second issue linked to MFA is related to the way the appliance handles certificate-based authentication. When this is enabled, Apache is responsible for verifying the certificate provided by the user and then transfers it to the back-end Flask API in the form of environment variables.
Для просмотра ссылки Войди
Simple representation of how certificate data is processed
The code responsible for retrieving those values in the API is shown below.
Код:
class Authenticate(Resource):
"""Authenticate the user"""
post_reqparser = reqparse.RequestParser()
post_reqparser.add_argument('userName', type = str, default = '', help = 'The user name.')
post_reqparser.add_argument('password', type = str, default = '', help = 'The password.')
post_reqparser.add_argument('domainName', type = str, default = '', help = 'The domain name is required.')
post_reqparser.add_argument('portalName', type = str, default = '', help = 'The portal name.')
post_reqparser.add_argument('deviceId', type = str, default = '', help = 'The device id.')
post_reqparser.add_argument('deivceType', type = str, default = '', help = 'The device type: activesync, outlook, or others.')
post_reqparser.add_argument('deviceAuthorization', type = str, default = '', help = 'The basic authentication string.')
post_reqparser.add_argument('clientSupportPDA', type = str, default = '', help = 'The client support PDA or not.')
post_reqparser.add_argument('SSL_CLIENT_VERIFY', type = str, dest = 'sslClientVerify')
post_reqparser.add_argument('SSL_CLIENT_S_DN', type = str, dest = 'subject')
post_reqparser.add_argument('SSL_CLIENT_I_DN', type = str, dest = 'issuer')
post_reqparser.add_argument('interactive', type = str, default = '', help = 'The login is interactive or not.')
swagger_post_reqparser = copy.deepcopy(post_reqparser)
if (API_UNIT_TEST_MODE == False):
post_reqparser.add_argument('HTTP_USER_AGENT', type = str, required = True, dest = 'userAgent', location = 'environ')
post_reqparser.add_argument('REMOTE_ADDR', type = str, required = True, dest = 'clientIpAddress')
post_reqparser.add_argument('SERVER_ADDR', type = str, required = True, dest = 'serverIpAddress')
post_reqparser.add_argument('SERVER_NAME', type = str, required = True, dest = 'hostName')
post_reqparser.add_argument('HTTP_HOST', type = str, required = True, dest = 'host', location = 'environ')
post_reqparser.add_argument('SSL_CLIENT_VERIFY', type = str, dest = 'sslClientVerify') # get the SSL_CLIENT_VERIFY value
post_reqparser.add_argument('SSL_CLIENT_S_DN', type = str, dest = 'subject') # Get the subject DN
post_reqparser.add_argument('SSL_CLIENT_I_DN', type = str, dest = 'issuer') # Get the issuer DN
post_reqparser.add_argument('Portal-Name', type = str, default = '', dest = 'envPortalName')
post_reqparser.add_argument('SERVER_PORT', type = str, required = True, dest = 'serverPort')
The big issue here is that since Flask does not specify where these parameters have to be taken from (i.e. the environment variables), it is possible to just send them along as POST parameters in a login request and therefore completely bypass the certificate validation process.
Код:
POST /cgi-bin/userLogin HTTP/1.1
[...]
Код:
userName=test&password=password1234&domainName=LocalDomain&portalName=VirtualOffice&SSL_CLIENT_VERIFY=U1VDQ0VTUw==&SSL_CLIENT_S_DN=L0M9REsvTD1BYXJodXMvTz1mcm9nZ2VyL0NOPXRlc3Q=&SSL_CLIENT_I_DN=L0MlM2RESy9MJTNkQWFyaHVzL08lM2Rmcm9nZ2VyK0NBL0NOJTNkdGhlaGVhdC5kaw==
U1VDQ0VTUw== in this request is just the base64-encoded version of SUCCESS.
After discovering these issues, I reported them to SonicWall who had them patched within 6 weeks or so, you can see the full timeline below.
- 16th of October 2024 : Reported issues to SonicWall
- 7th of November 2024 : SonicWall indicate all issues have been fixed
- 8th of November 2024 : I confirm they have actually been fixed
- 25th of November 2024 : CVEs assigned
- 5th of December 2024 : Patches and advisory released
- CVE-2024-40763 – Heap buffer overflow vulnerability – 8.1 (High)
- CVE-2024-45318 – Stack buffer overflow vulnerability – 8.1 (High)
- CVE-2024-45319 – Certificate-based authentication bypass – 6.3 (Medium)
- CVE-2024-53702 – Insecure randomness – 5.3 (Medium)
- CVE-2024-53703 – Apache module stack-based buffer overflow vulnerability – 8.1 (High)
Для просмотра ссылки Войди