This will (hopefully) be a short story about a bug I found some time ago while auditing a .NET service from an OEM. It should be interesting as I have yet to find a description of how to exploit a similar condition.
Our service was running as SYSTEM
and needed to periodically
execute some other utilities as part of its workflow. Before running these
auxiliary tools, it would check if the executable was properly signed by the
vendor. Something like this:
public void CallAgent()
{
string ExeFile = "C:\\Program Files\\Vendor\\Util.exe";
if (!new Signtool().VerifyExe(ExeFile))
return;
// Execute Agent here
}
This is where it gets interesting. Of course we can’t
control anything at that Program Files
location, but what is that VerifyExe
method doing?
internal class Signtool
{
private const string SignToolPath = "C:\\Windows\\Temp\\signtool.exe";
private void ExtractSignTool()
{
byte[] signtool = QuatService.Resource1.signtool;
using (FileStream fileStream = new FileStream("C:\\Windows\\Temp\\signtool.exe", FileMode.Create))
fileStream.Write(signtool, 0, signtool.Length);
}
private void DeleteSignTool()
{
File.Delete("C:\\Windows\\Temp\\signtool.exe");
}
private bool Verify(string arg)
{
this.ExtractSignTool();
Process process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
Path.GetDirectoryName(this.GetType().Assembly.Location);
process.StartInfo.FileName = "C:\\Windows\\Temp\\signtool.exe";
process.StartInfo.Arguments = arg;
process.Start();
process.WaitForExit();
this.DeleteSignTool();
return process.ExitCode == 0 || process.ExitCode == 2;
}
public bool VerifyExe(string ExeFile)
{
return this.Verify("verify /pa \"" + ExeFile + "\"");
}
}
The code simply extracts a signature verification tool that it has embedded in C:\Windows\Temp
as part of its resources, executes it to verify the target executable, and then deletes the tool as if nothing ever happened.
Did you catch the bug? The issue is in the FileMode.Create
flag that gets passed as part of the FileStream call used to write the file.
What is object squatting?
I first read about squatting attacks in “The Art of Software Security Assessment” (which I highly recommend by the way). Essentially, squatting is an attack where you create an object before the legitimate application does. You can then manipulate the object and affect the normal behavior of the application. If an application is not careful and attempts to create a named object (such as Mutex, an Event, a Semaphore, etc.) in the global namespace, it might open an existing object instead, because another application could have already created an object with the exact same name. In this case, the Create method will succeed (https://docs.microsoft.com/en-us/windows/win32/sync/object-names):
This same method can be used for file squatting: the application acts as if it has created a file when it has actually opened an existing file.
There are two conditions necessary for this to be exploitable:
- The
dwCreationDisposition
parameter of theCreateFile
function must be set incorrectly, leading the application to open an
existing file instead of creating a new one. An incorrect setting is any
setting exceptCREATE_NEW
. - The location where the file is being created must
be writeable by potentially malicious users.
So in C/C++ code, if you see a call
to CreateFile
using CREATE_ALWAYS
, it should raise a flag:
In .NET code, FileMode.Create
maps
to CREATE_ALWAYS
:
Exploitation
At this point, we have a confirmed file squatting vulnerability:
- The service is not using
FileMode.CreateNew
. - The location
C:\Windows\Temp
is writable by authenticated
users.
We also have a race condition
because there is a time window between when signtool.exe
is extracted and when
it is executed.
Therefore, we can exploit this vulnerability by leveraging Hardlinks and OpLocks:
The steps would be the following:
- Create
a directory such asC:\Users\Public\Exploit
. - Create
a file named dummy.txt inside the directory. - Place
payload.exe
inside the directory. - Create
the Hardlink inC:\Windows\Temp\Signtool.exe
to point toC:\Users\Public\Exploit\Dummy.txt
. - Set
an OpLock on dummy.txt. - When
the OpLock triggers, recreate the Hardlink to point to payload.exe (we can do
this because the file is ours and the ACL hasn’t changed).
Not so fast! If we check the
behavior of the vulnerable “QuatService” with ProcMon, we see there are
actually five calls to CreateFile
instead of just three:
The first CreateFile
is used by
FileStream to write the signtool to disk. The second, third, and fourth calls
are all part of the inner workings of CreateProcess
. The final CreateFile
is
called with delete access in order to erase the file.
At a practical level, because of
the short time window, the two additional CreateFile
calls from CreateProcess
could interfere with our goal. I found that the best settings for reliable,
reproducible results were:
- Use a second OpLock on dummy.txt after the first
one is hit. - Call Sleep(1) to skip the third CreateFile
(second one from CreateProcess). - Attempt to create the Hardlink to payload.exe in
a loop. This is necessary because the Hardlink creation could fail due to the
fact the service could still hold the handle from the third CreateFile.
Here is the code for a functional exploit for the vulnerability (tested on Windows 10 19041 and 18363):
https://github.com/IOActive/FileSquattingExample/blob/master/SquatExploit/SquatExploit/SquatExploit.c
Video demo of the exploit working:
The vulnerable service is included in the same GitHub project in case you want to play with it. If you come up with a better approach to increase the reliability of the exploit, please send me a message.
Mitigations
As already discussed, to avoid this situation there are two options:
- Use
CREATE_NEW / FileMode.CreateNew
and
handle the potential error caused by an existing file. - Write
to a protected filesystem location.
What about the path redirection mitigations?
- The Hardlink mitigation doesn’t apply here because we’re creating a link to our own file.
- The
SYSTEM %TEMP%
change is not implemented yet. Even though this mitigation will definitely fix the vulnerability, it is worth noting that there will be still room for squatting other directories:- C:\Windows\Tasks
- C:\windows\tracing
- C:\Windows\System32\Microsoft\Crypto\RSA\MachineKeys
- C:\Windows\System32\spool\drivers\color
- C:\Windows\SysWOW64\Tasks\Microsoft\Windows\PLA\System
References
- https://www.slideshare.net/OWASPdelhi/abusing-symlinks-on-windows
- https://offsec.almond.consulting/intro-to-file-operation-abuse-on-Windows.html
- https://github.com/n3k/PlayReparsePointsAndOplocks
- https://docs.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces
- The Art of Software Security Assessment
- Keeping Windows Secure (David Weston): https://github.com/dwizzzle/Presentations/blob/master/David%20Weston%20-%20Keeping%20Windows%20Secure%20-%20Bluehat%20IL%202019.pdf
More on Object Squatting mitigations: