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.
Figure 1: predeploy installer with VPN Posture (HostScan) selected (default option)
The service then listens at 127.0.01 on port 1023:
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:
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).
Figure 4: verify_peer function checks if current process is running as SYSTEM
Figure 5: verify_peer calls GetExtendedTcpTable function with TCP_TABLE_OWNER_PID_CONNECTIONS parameter
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).
Figure 7: process_ipc_message checks that the first DWORD is 0x2E24
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:
Figure 9: library names that can be used for DLL Hijacking
The packet for the
priv_file_copy command must have the following structure:
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:
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:
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:
- Check where the libhostscan.dll library is located and copy it to the
%TEMP%\Cisco\Cisco HostScandirectory. The library may be present in either the
%PROGRAMFILES(X86)%\Cisco\Cisco HostScan\libor in the
%PROGRAMFILES(X86)%\Cisco\Cisco AnyConnect Secure Mobility Client\Posturedirectory.
- 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.
- 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:
priv_file_copycommand that will copy the libhostscan.dll library from the
%TEMP%\Cisco\Cisco HostScandirectory to the
%PROGRAMFILES(X86)%\Cisco\Cisco HostScan\libdirectory. (If the library already exists, it will be overwritten.)
- A second
priv_file_copycommand that will copy your IPHLPAPI.DLL from
%TEMP%\Cisco\Cisco HostScandirectory to the
%PROGRAMFILES(X86)%\Cisco\Cisco HostScan\libdirectory. Remember that your DLL must match the name and all the exported functions of the original system DLL because its loaded by the OS.
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.
- 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:
Once you get the .exe file, you can hollow and inject it into a digitally signed binary using a tool like ProcessHollowing32-64.
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.