Analysis of Cisco AnyConnect Posture (HostScan) Local Privilege Escalation: CVE-2021-1366

Authored by: Marcos Accossatto

On August 5th, ethical hacker and cybersecurity professional Antoine Goichot posted on twitter that three vulnerabilities he had discovered on Cisco AnyConnect (CVE-2020-3433, CVE-2020-3434, and CVE-2020-3435) were now public. The next day, he published a follow-up blogpost on github.

That lead to an investigation by the Core Security team to find additional vulnerabilities on the program.

After some digging, we found there was a service listening in localhost on port 1023: The Security Service of AnyConnect Posture (ciscod.exe).

Cisco AnyConnect Posture is an optional module that you can install along with AnyConnect Secure Mobility Client. This module enables the VPN client to identify the operating system, antivirus, anti-spyware, and firewall software installed on the host. An application called HostScan gathers this information, so a Posture assessment requires HostScan to be installed on the host.

If you only have the VPN client installed, then this service should not be present in your system. But if you have selected the VPN Posture in the predeploy installer (or if your IT department did it in the webdeploy installer), then the service is present.

Image
Figure 1: predeploy installer with VPN Posture (HostScan) selected (default option)

The service then listens at 127.0.01 on port 1023:

Image
Figure 2: ciscod.exe service listening at 127.0.0.1 on port 1023

Through this port, the service communicates with other processes using its own Inter Process Communication (IPC) mechanism, accepting commands given in a certain packet format. The service is constantly looping, waiting for peers to connect to it:

Image
Figure 3: main loop of run function of ciscod.exe service

Once a peer connects, it is verified using the function verify_peer. This function first checks if the service process is running as SYSTEM (Figure 4). If so, then it loads the library Iphlpapi.dll and gets the address of the GetExtendedTcpTable function. This function is called with the TCP_TABLE_OWNER_PID_CONNECTIONS TableClass parameter to get a structure that contains a table of process IDs (PIDs) and the IPv4 TCP links that are context bound to these PIDs (Figure 5). Finally, the peer full path to the executable is obtained and should then be checked to see if it is signed by “Cisco Systems, Inc.” (Figure 6).

Image
Figure 4: verify_peer function checks if current process is running as SYSTEM
Image
Figure 5: verify_peer calls GetExtendedTcpTable function with TCP_TABLE_OWNER_PID_CONNECTIONS parameter
Image
Figure 6: verify_peer gets executable full path and verifies its signature

If those checks are passed, then the command is processed by the process_ipc_message function. This function first checks that the first DWORD (4 bytes) of the packet is 0x2E24 (11812). This value is the expected length of the following data, so we can conclude that the size of a command packet is 0x2E28 (11816) bytes (Figure 7). Then the following DWORD is taken from the packet and compared with the list of available commands (Figure 8).

Image
Figure 7: process_ipc_message checks that the first DWORD is 0x2E24
Image
 Cisco_AnyConnect_Posture_LPE_Figure_8_list_of_available_commands.png
Figure 8: list of available commands

There are 15 commands:

  • priv_file_copy (opcode 0x20)
  • priv_file_rename (opcode 0x23)
  • priv_file_make_executable (opcode 0x22)
  • priv_dir_create (opcode 0x21)
  • priv_enable_firewall (opcode 0x31)
  • priv_disable_firewall (opcode 0x32)
  • priv_add_firewall_rule (opcode 0x33)
  • priv_get_firewall_status (opcode 0x34)
  • priv_enable_antimalware (opcode 0x40)
  • priv_update_antimalware (opcode 0x41)
  • priv_check_rtp_antimalware (opcode 0x42)
  • priv_get_def_date_antimalware (opcode 0x43)
  • priv_get_version_antimalware (opcode 0x44)
  • priv_proc_path (opcode 0x50)
  • priv_get_device_id (opcode 0x60)

The names suggest their function. Now, since there is no “execute program” command, it’s best to use the priv_file_copy command. This command allows us to copy a file from any location, to a subdirectory in the %PROGRAMFILES(X86)%\Cisco\Cisco HostScan directory. The function checks for directory traversal (“..”) so it is not possible to escape from the destination directory. Also, the source file must be inside a \Cisco\Cisco HostScan directory.

Since the previous command allows us to copy any file to any directory inside %PROGRAMFILES%\Cisco\Cisco HostScan, we could copy a library to the \bin directory, so when the service is started, that library will be loaded and executed (DLL Hijacking). There are several library names that can be used, for example Dbghelp.dll:

Image
Figure 9: library names that can be used for DLL Hijacking

The packet for the priv_file_copy command must have the following structure:

Image
Figure 10: priv_file_copy command packet structure

Unfortunately, as a normal user, we cannot stop/start the service so we will have to wait or force the computer to restart in order to escalate privileges.

Since restarting was not sufficient, we tried with other commands to see if we could trigger the local privilege escalation (LPE).

Ultimately, the command priv_get_device_id (opcode 0x60) solved the problem.

The packet must have the following structure:

Image
Figure 11: priv_get_device_id command packet structure

When the service receives this command, it tries to load the libhostscan.dll library from the \lib directory. That was all that we needed to trigger the LPE, so we did not waste any more time figuring out what the command really does.

If VPN Posture was installed using the predeploy installer, the library will be present in the \lib directory. If the webdeploy installer was used instead, then it would be located in the %ProgramFiles(x86)%\Cisco\Cisco AnyConnect Secure Mobility Client\Posture directory. In either case, the library is available.

Since this DLL is loaded by the service and it must be in the \lib directory, you can look at its Import Table to select any of the system DLL that it uses and perform a DLL Hijacking:

Image
Figure 12: imported functions of libhostscan.dll

In our case, we selected the IPHPLAPI.DLL library.

 

To summarize, the sequence to trigger the LPE is as follows:

  1. Check where the libhostscan.dll library is located and copy it to the %TEMP%\Cisco\Cisco HostScan directory. The library may be present in either the %PROGRAMFILES(X86)%\Cisco\Cisco HostScan\lib or in the %PROGRAMFILES(X86)%\Cisco\Cisco AnyConnect Secure Mobility Client\Posture directory.
  2. Start the service’s executable file (ciscod.exe) in a suspended state. The service’s executable file (ciscod.exe) is a good candidate for this since it is digitally signed by Cisco.
  3. Perform a process hollowing of the suspended process and replace it with a process that sends the following three commands to 127.0.0.1:1023:
    • A priv_file_copy command that will copy the libhostscan.dll library from the %TEMP%\Cisco\Cisco HostScan directory to the %PROGRAMFILES(X86)%\Cisco\Cisco HostScan\lib directory. (If the library already exists, it will be overwritten.)
    • A second priv_file_copy command that will copy your IPHLPAPI.DLL from %TEMP%\Cisco\Cisco HostScan directory to the %PROGRAMFILES(X86)%\Cisco\Cisco HostScan\lib directory. Remember that your DLL must match the name and all the exported functions of the original system DLL because its loaded by the OS.
    • A priv_get_device_id command.

             Keep in mind that after every command, a recv of size 0x2E28 (11816) bytes must be performed to function properly.

             Also, a one or two second delay between commands increases the reliability.

  1. Resume the main thread of the suspended process.

And that is how we can execute code with SYSTEM privileges!

For a simple PoC (Proof of Concept), the code of the injected executable should be something along these lines:

Image
 Cisco_AnyConnect_Posture_LPE_Figure_13_POC_part_1_code_of_injected_executable
Image
Cisco_AnyConnect_Posture_LPE_Figure_14_POC_part_2_code_of_injected_executable

Once you get the .exe file, you can hollow and inject it into a digitally signed binary using a tool like ProcessHollowing32-64.

Cisco’s Patch

Cisco fixed the vulnerability in version 4.9.05042 of AnyConnect Posture by enforcing digital signature verification in the priv_file_copy IPC command. This makes the DLL hijacking attack useless since you cannot copy a malicious DLL in the app directory.

Another way to avoid the DLL hijacking of the system DLLs is to enable the process mitigation load policy. For example, you could use the following powershell command (as administrator):

Set-ProcessMitigation -Name "C:\Program Files (x86)\Cisco\Cisco HostScan\bin\ciscod.exe" -Enable PreferSystem32

After restarting the service, it will try to load all DLLs from system32 directory first, then in the application directory in the standard DLL search order. For more information about this mitigation read this blogpost from the Microsoft Security Response Center.

For updates, check the Cisco advisory for this vulnerability.

You can check our full advisory here.

Learn About Other Advisories!

CTA Text

Browse the CoreLabs Cybersecurity Threat Advisories page to find out about other vulnerabilities discovered by the CoreLabs research team.

BROWSE THE LIBRARY