After presenting my findings on the Swisscom router at the CybSecConference last year, I started looking for a new product to analyze. I quickly found that it’s possible to download virtual “demo” appliances of Citrix products, so I went on to download a Netscaler VPX, which at the time was at version 11.10 (b50.10). The goal as always was to discover a way to compromise the device remotely, which is what led me to discover a heap overflow vulnerability which allows an authenticated user to compromise the device with root privileges. During the research, I (un)fortunately wasn’t able to find a way to exploit the flaw without credentials.
A heap overflow in the “ping” functionality allows an authenticated VPN user to trigger a use-after-free condition in order to execute arbitrary commands on the appliance. (CVE-2017-7219)
The following Metasploit module can be used to exploit the vulnerability (use at your own risk…), though it will probably only function against the version that was analyzed.
As mentioned above, I began by downloading the virtual appliance and started it up on my machine. Since I’ve never used or configured a Netscaler appliance in the past, it took a while to get things going and configuring it in some kind of standard mode.
Once the appliance is started, it is possible to log into the console with the standard nsroot account. This gives access to a “limited” shell, but Citrix were nice enough to add a shell command which gives root access to the box, so I used this to extract the filesystem and analyze what was going on.
Going through the various files on the system, I found one that seemed promising which was named /netscaler/nsvpnd. As it’s name hints at, it is used to handle requests sent to the VPN web interface. Though only authenticated requests seem to get here, as authentication itself is performed by another binary on the system.
One of the requests that is performed by the nsvpnd binary is the ping request.
This results in the following HTTP request:
POST /cvpn/aHR0cDovLzEyNy4wLjAuMQ/fs/ping HTTP/1.1 Host: [...] Cookie: NSC_AAAC=b2f85f0b72ef21c82eac5ac4d314a4170af182cd945525d5f4f58455e445a4a42; NSC_FSSO=0 DNT: 1 Connection: close Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded Content-Length: 41 host=127.0.0.1&nsc_nonce=kPIueluG6ubF37J0
Apparently the /cvpn/aHR0cDovLzEyNy4wLjAuMQ part of the URL is not actually required, so it can safely be removed. In any case, this request is eventually handled by one of two vulnerable functions that contain an unbounded strcpy with our host parameter, as shown below.
This is where the overflow happens, though we are not overwriting a stack variable, but one of the members of a struct, which is expected to be at most 256 bytes long. Our parameter on the other hand can go up to 512 bytes, which is what allows us to overflow this buffer.
So it is possible to write up to 256 bytes after the host member of the structure, therefore overwriting any other members of the structure after the host, which is where things get interesting. One of the following members is actually a pointer to another structure (a parameter list) on the heap which was previously allocated and eventually gets free‘d by the application when the request has completely been processed. This means we can essentially free an arbitrary memory location.
Before going any further, a quick analysis of the system and binary show that FreeBSD uses jemalloc instead of dlmalloc, the heap is not executable, but the stack is, and ASLR is not enabled (this was 2016 after all). Another thing that was helpful in exploiting this particular issue is that all requests to the web interface are handled by one single process, which means we can actually interact many times with the process by sending multiple HTTP requests if required.
At this point, my idea to gain code execution was the following:
- Find a function pointer somewhere in the application, as well as the size that was used to allocate that memory
- Free the memory address of this function pointer
- On the next malloc of the appropriate size, the same address should be returned
- Overwrite the function pointer with user-controlled data (a pointer to my shellcode) when it is copied to this memory address
- Trigger the function pointer to call my shellcode
The only remaining problem is getting a shellcode to some predictable location. Thankfully, as mentioned earlier, ASLR is disabled, the stack is executable and the value we send in our host parameter is actually stored on the stack! All we need is to get this address and plug it into the function pointer to get the shellcode to execute. Obviously, despite ASLR being disabled, the stack will not always be exactly at the same place, so I used a super-l33t technique consisting of pre-pending my shellcode with lots of NOPs (because 2016).
So we can now break down each step and look at how we can achieve them:
- With some reverse engineering and debugging, I found one function pointer that was always allocated at address
0x2840a0c0. This function seems to be used to decode parameters sent in the HTTP requests. The memory address is initially allocated at
0x08097fb9with a call to malloc(32).
- Use the overflow to overwrite the pointer to the parameter list with
0x2840a0c0. The address is then free‘d when the request has finished being processed. Here, we also need to take a note of where our host parameter is located on the stack, as this is where we will store our shellcode.
- While searching through the binary’s code, I found one place where a malloc is called with a length which can be specified by the VPN user directly. This is when providing the username and password to log into a SMB server. There may be other parts of the code that could be exploited in a more reliable manner, but this is the first I found and decided to go with it. The only problem is that it means we need to initiate a SMB login to a server that is accessible to the Netscaler appliance.
- As long as our password is between 16 and 32 characters, the previously free‘d address is returned and we can therefore overwrite the function pointer with the value of our password. It must therefore be the address of our shellcode, which we discovered was placed on the stack when performing the ping.
- The function pointer is actually called at regular intervals by the application while processing data, so we can just wait until it is called to get our code executed.
As you’ve probably deduced by now, in order to exploit the vulnerability, we are going to use two separate HTTP requests. The first one is used to put the shellcode on the stack and trigger the overflow, while the second is used to overwrite the function pointer with the address of our shellcode and actually execute our payload. This is summarised here:
Request 1 (ping host)
→ Start of host value contains shellcode which is conveniently placed on the stack
→ Use overflow to overwrite pointer to parameter list with
→ When the request has been entirely processed, the program frees the address
Request 2 (smb login)
→ Specify a password parameter of length between 16 and 32, this forces malloc to return the address that was previously freed.
→ Password value (shellcode location) overwrites function pointer that was previously located there
→ While processing the request, the overwritten function pointer is called, executing the shellcode
So that’s pretty much it. The following MSF module, should be able to exploit the flaw, but use it at your own risk. I’ve only tested it on a controlled lab environment.
For those of you who participated in our Insomni’hack teaser this year, you’ll notice many similarities with “The Great Escape – part 2” challenge, as it was very much inspired by this flaw.
- 08.12.2016: Initial report sent to Citrix
- 09.12.2016: Case opened by Citrix to investigate the issue
- 14.12.2016: Vulnerability acknowledged and reproduced by Citrix team
- February-March 2017: Rollout of fixed Netscaler versions
- 12.04.2017: Release of security bulletin: https://support.citrix.com/article/CTX222657