In this post I will discuss a Windows Defender Antivirus bypass I discovered and reported to Microsoft on May 26th 2020. The bypass affects the current versions of Windows Defender deployed with Windows Server 2016/2019, where the Web Server role is installed. This post focuses on the road I took to get to the bypass as well, so for the TL;DR go to "Bypassing Windows Defender Antivirus 2016 using automatic exclusions" section.
Setting the stageI recently worked on a penetration test for an asp.net application. The setup was pretty standard for a small asp.net application, it was deployed in amazon AWS with a web tier and database tier. The web tier, where the application lived was configured on a Windows 2016 server with the Web Server role installed. This asp.net application integrated with a much larger suite of applications deployed in the same amazon AWS environment (14 other windows servers). Below i've generated some screenshots from some test systems help visualize the scenario.
In the early stages of the assessment, I fuzzed the application url with my favorite fuzzing tool ffuf. I discovered a URL that contained several unauthenticated web services. The web services had their description documents enabled, which disclosed how I should interact with them. The most interesting of the web services allowed me to generate a base64 encoded request to upload a file to the application. The web service allowed any file type to be uploaded and the response generated from the request disclosed the location of the uploaded file on the web server. This situation is obviously the perfect storm of bad, and often in my line of work, game over.
Using the file upload web service I placed a asp.net web shell on the web server and browsed to the location of the file reflected in the response. I now had command line server access running as the "IIS apppool\defaultapppool" user. This user is extremely limited but allowed me to gather more information about the environment for the assessment.
Getting an interactive shell
The next step was to get an interactive meterpreter shell so I could continue post-exploitation, privilege escalation and try to gain access to the other servers in the amazon AWS environment. We all have our preferred tools for executing shell-code and getting interactive shells on systems. One of my favorite tools is TrustedSec' unicorn. I've used this tool many times in the past with success, but as of recently Windows Defender Antivirus has done an excellent job of detecting it. Good for Microsoft, bad for me.
Writing undetected binaries
After a failed attempt with TrustedSec' unicorn, it was time to roll up my sleeves and look for another technique. After some google-fu searching, I found a recent article about a technique that used golang into inject shell-code into memory. The article listed the github repository with some example code. The code leverages the syscall package in golang to call the NewLazyDLL method to load Kernel32.dll. Once Kernel32.dll is loaded, it can be used for addressing and memory allocation. The compiled code takes a msfvenom payload in hex format as a command line argument.
I did make some modifications to this code before trying it. The code had been around for a while and would likely be detected as is. The first thing I did rename all the variables in the code. I then changed the code to retrieve the msfvenom payload from an url (over tls), instead of a command-line parameter. The last change was an additional command-line parameter that needed to be present to execute. If the parameter wasn't present, the code would exit 0. This was in hopes of bypassing any sandbox analysis tools. The overall idea would be a hollowed-out binary that would seemingly do nothing, unless you told it what to do.
I used powershell to download my newly compile binary to the server from my asp.net web shell .
I waited a few minutes to see if the binary had been deleted by Windows Defender Anti-virus and it hadn't so I executed the binary. My meterpreter listener received a session...all is well, so I thought.
Getting detected by Windows Defender Anti-virus
I thought I had successfully bypassed Windows Defender Antivirus with my custom golang binary, but quickly learned that wasn't true. I tried interacting with the meterpreter session, which seem to work until I issued any meterpreter commands. I tried a simple "getuid" command and received an error.
After trying to issue a few more commands, I check the directory where my binary had been uploaded. Windows Defender Antivirus deleted the file.
I confirmed it was Windows Defender Antivirus that deleted the file (and not another AV product that was installed) by creating my own Windows 2016 server in amazon AWS and executing the same steps there. When I checked the quarantined items, I could see it in fact detected the binary once I issue the "getuid" meterpreter command.
Getting to know Windows Defender Anti-virus
After getting some more coffee and having time to think, I started researching Windows Defender Antivirus. I was confident that my binary was making it past the "Always-on scanning" feature of Windows Defender Antivirus. My assumption was that my binary was being picked up by the "real-time protection"(process behavior monitoring and other heuristics).
At some point during my research I found a Microsoft article titled "Configure Microsoft Defender Antivirus exclusions on Windows Server". At the very top of the article, I found an interesting piece of information that was previously unknown to me:
"Microsoft Defender Antivirus on Windows Server 2016 and 2019 automatically enrolls you in certain exclusions, as defined by your specified server role. See the list of automatic exclusions (in this article). These exclusions do not appear in the standard exclusion lists that are shown in the Windows Security app."
My next stop was to review the list of automatic exclusions. It was in this article I found a section on the "Web Server exclusions". This section of the article was described as:
"This section lists the folder exclusions and the process exclusions that are delivered automatically when you install the Web Server role."
At the very bottom of the "Web Server exclusions" was a list of three process exclusions. The last of the three listed was a process exclusion for the windows version of PHP.
This sounded pretty promising, but I needed to confirm what was exactly meant by a "Process exclusion". I found a article describing the differences between each of exclusion types. The highlighted part below, gave me the information I needed and at that point, I had a plan.
Bypassing Windows Defender Antivirus 2016 using automatic exclusions
From the enumeration I did earlier on the Windows 2016 server, I knew that PHP was not a component installed on this Web Server. I also knew from previous research that by default, on windows systems, authenticated users can create directories under the c:\ drive. An authenticated user can be the "IIS apppool\defaultapppool" user, which my asp.net web shell was running as.
So with this new information I used my asp.net web shell to create the directory c:\PHP5433.
I then used powershell to download my binary to the c:\PHP5433 and named the file php-cgi.exe.
I again executed the binary but under its new directory and filename (c:\PHP5433\php-cgi.exe) and created a meterpreter session as expected.
I again interacted with the session and issued a "getuid" command (and crossed my fingers). Sweet success!
All meterpreter commands executed properly and I was able to privilege escalate to NT AUTHORITY \ SYSTEM via the juicy potato exploit. This confirmed that the process exclusion was working properly and I had successfully bypassed Windows Defender Antivirus.
Reporting the Windows Defender Antivirus bypass to Microsoft
I reported the Windows Defender Antivirus bypass to Microsoft on May 26th 2020. I recreated an environment similar to the environment from my pentest. I reported the steps to reproduce the issue and sent a video of the bypass being used. In my opinion, I felt it was a bad assumption to deploy an automatic exclusion for PHP solely based on if the "Web server role" is installed. Microsoft did not implement any verification checks to confirm PHP was in fact installed.
On June 10 2020, I received a response from Microsoft that they wouldn't be fixing the vulnerability in the current version, but will consider it for future versions. They consider this expected behavior.
I understand from Microsoft's perspective that this is expected behavior, Windows Defender Antivirus is doing exactly what they've asked it to. What I don't understand the logic behind the exclusion itself in the first place? The default installation path for PHP on windows is not c:\PHP5433. If we are looking at this from a versioning perspective, the latest version of PHP from the Web platform installer is 7.x, which doesn't fit the naming convention. Perhaps this exclusion was added for a very specific edge-case along the way and never removed?
It sounds like they are open to fixing the issue in later versions. Below are some possible fixes I've thought of:
1. If the current exclusion needs to exist for some unforeseen tribal knowledge reason, move the exclusion to a directory that already exists and can't be modified by a low-level authenticated user.
2. Have the Web platform installer kick off a process to add the Windows Defender Antivirus exclusion (to the correct installation path) once a user has installed PHP.
3. Have Windows Defender Antivirus run a checksum against any binaries that are included in automatic exclusions to verify they are authentic.
Mitigating the issue
The bypass can be mitigated by disabling automatic exclusions and configuring your own exclusions manually. There isn't a way to disable one or multiple automatic exclusions, its an all or nothing situation. So if you do try mitigate the issue this way, you could risk taking a performance hit or experience data corruption according to the documentation.